diff --git a/composer.lock b/composer.lock
index 025d5bf..b6d299b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2586,6 +2586,131 @@
             "time": "2015-08-29 16:16:56"
         },
         {
+            "name": "jcalderonzumba/gastonjs",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jcalderonzumba/gastonjs.git",
+                "reference": "75a9791f7374948bfa66ad283dda1502a883c14f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jcalderonzumba/gastonjs/zipball/75a9791f7374948bfa66ad283dda1502a883c14f",
+                "reference": "1a0d9275fa5abe89e4869ce32cac7dd73ae7bd26",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "~5.0|~6.0",
+                "php": ">=5.4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.6",
+                "silex/silex": "~1.2",
+                "symfony/phpunit-bridge": "~2.7",
+                "symfony/process": "~2.1"
+            },
+            "type": "phantomjs-api",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Zumba\\GastonJS\\": "src"
+                }
+            },
+            "autoload-dev": {
+                "psr-4": {
+                    "Zumba\\GastonJS\\Tests\\": "tests/unit"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Juan Francisco Calderón Zumba",
+                    "email": "juanfcz@gmail.com",
+                    "homepage": "http://github.com/jcalderonzumba"
+                }
+            ],
+            "description": "PhantomJS API based server for webpage automation",
+            "homepage": "https://github.com/jcalderonzumba/gastonjs",
+            "keywords": [
+                "api",
+                "automation",
+                "browser",
+                "headless",
+                "phantomjs"
+            ],
+            "support": {
+                "source": "https://github.com/dawehner/gastonjs/tree/guzzle6"
+            },
+            "time": "2015-10-05 22:17:26"
+        },
+        {
+            "name": "jcalderonzumba/mink-phantomjs-driver",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jcalderonzumba/MinkPhantomJSDriver.git",
+                "reference": "10d7c48c9a4129463052321b52450d98983c4332"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jcalderonzumba/MinkPhantomJSDriver/zipball/10d7c48c9a4129463052321b52450d98983c4332",
+                "reference": "10d7c48c9a4129463052321b52450d98983c4332",
+                "shasum": ""
+            },
+            "require": {
+                "behat/mink": "~1.6",
+                "jcalderonzumba/gastonjs": "~1.0",
+                "php": ">=5.4",
+                "twig/twig": "~1.8"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.6",
+                "silex/silex": "~1.2",
+                "symfony/css-selector": "~2.1",
+                "symfony/phpunit-bridge": "~2.7",
+                "symfony/process": "~2.3"
+            },
+            "type": "mink-driver",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Zumba\\Mink\\Driver\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Juan Francisco Calderón Zumba",
+                    "email": "juanfcz@gmail.com",
+                    "homepage": "http://github.com/jcalderonzumba"
+                }
+            ],
+            "description": "PhantomJS driver for Mink framework",
+            "homepage": "http://mink.behat.org/",
+            "keywords": [
+                "ajax",
+                "browser",
+                "headless",
+                "javascript",
+                "phantomjs",
+                "testing"
+            ],
+            "time": "2015-10-05 18:24:44"
+        },
+        {
             "name": "mikey179/vfsStream",
             "version": "v1.6.0",
             "source": {
@@ -3674,7 +3799,9 @@
         "behat/mink-goutte-driver": 0,
         "mikey179/vfsstream": 0,
         "phpunit/phpunit": 0,
-        "symfony/css-selector": 0
+        "symfony/css-selector": 0,
+        "jcalderonzumba/mink-phantomjs-driver": 20,
+        "jcalderonzumba/gastonjs": 20
     },
     "prefer-stable": true,
     "prefer-lowest": false,
diff --git a/core/composer.json b/core/composer.json
index 2489069..5caf498 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -36,7 +36,9 @@
     "behat/mink-goutte-driver": "~1.2",
     "mikey179/vfsStream": "~1.2",
     "phpunit/phpunit": "~4.8",
-    "symfony/css-selector": "2.7.*"
+    "symfony/css-selector": "2.7.*",
+    "jcalderonzumba/mink-phantomjs-driver": "dev-master",
+    "jcalderonzumba/gastonjs": "dev-master"
   },
   "replace": {
     "drupal/action": "self.version",
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 41c6eb0..b3a69cd 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -636,9 +636,9 @@ function drupal_valid_test_ua($new_prefix = NULL) {
   // string.
   $http_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL;
   $user_agent = isset($_COOKIE['SIMPLETEST_USER_AGENT']) ? $_COOKIE['SIMPLETEST_USER_AGENT'] : $http_user_agent;
-  if (isset($user_agent) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $user_agent, $matches)) {
+  if (isset($user_agent) && preg_match("/^(simpletest\d+):(.+):(.+):(.+)$/", $user_agent, $matches)) {
     list(, $prefix, $time, $salt, $hmac) = $matches;
-    $check_string =  $prefix . ';' . $time . ';' . $salt;
+    $check_string =  $prefix . ':' . $time . ':' . $salt;
     // Read the hash salt prepared by drupal_generate_test_ua().
     // This function is called before settings.php is read and Drupal's error
     // handlers are set up. While Drupal's error handling may be properly
@@ -695,8 +695,8 @@ function drupal_generate_test_ua($prefix) {
   }
   // Generate a moderately secure HMAC based on the database credentials.
   $salt = uniqid('', TRUE);
-  $check_string = $prefix . ';' . time() . ';' . $salt;
-  return $check_string . ';' . Crypt::hmacBase64($check_string, $key);
+  $check_string = $prefix . ':' . time() . ':' . $salt;
+  return $check_string . ':' . Crypt::hmacBase64($check_string, $key);
 }
 
 /**
diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
index ed64f08..08f48f2 100644
--- a/core/modules/simpletest/src/BrowserTestBase.php
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -27,6 +27,7 @@
 use Drupal\Core\Url;
 use Drupal\user\UserInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Zumba\Mink\Driver\PhantomJSDriver;
 
 /**
  * Provides a test case for functional Drupal tests.
@@ -182,7 +183,7 @@
    */
   protected $customTranslations;
 
-  /*
+  /**
    * Mink class for the default driver to use.
    *
    * Shoud be a fully qualified class name that implements
@@ -194,7 +195,7 @@
    */
   protected $minkDefaultDriverClass = '\Behat\Mink\Driver\GoutteDriver';
 
-  /*
+  /**
    * Mink default driver params.
    *
    * If it's an array its contents are used as constructor params when default
@@ -217,6 +218,13 @@
   protected $mink;
 
   /**
+   * The base URL.
+   *
+   * @var string
+   */
+  protected $baseUrl;
+
+  /**
    * Initializes Mink sessions.
    */
   protected function initMink() {
@@ -226,6 +234,14 @@ protected function initMink() {
     $this->mink->registerSession('default', $session);
     $this->mink->setDefaultSessionName('default');
     $this->registerSessions();
+
+    // Fire up the first request to the front page in order to be able to  set
+    // a cookie later.
+    // @fixme This is done to circumvent:
+    // WebDriver\Exception\UnableToSetCookie: {"errorMessage":"Unable to set Cookie: no URL has been loaded yet","request":{"headers":{"Accept":"application/json;charset=UTF-8","Content-Length":"164","Content-Type":"application/json;charset=UTF-8","Host":"127.0.0.1:8910"},"httpVersion":"1.1","method":"POST","post":"{\"cookie\":{\"name\":\"SIMPLETEST_USER_AGENT\",\"value\":\"simpletest472558;1441488302;55eb5dae78eeb7.36607058;IxhRvB7dhbDAMurfBshqgUwEOpAPPKybnJMv0JgaG8Q\",\"secure\":false}}","url":"/cookie","urlParsed":{"anchor":"","query":"","file":"cookie","directory":"/","path":"/cookie","relative":"/cookie","port":"","host":"","password":"","user":"","userInfo":"","authority":"","protocol":"","source":"/cookie","queryKey":{},"chunks":["cookie"]},"urlOriginal":"/session/91aef6b0-5414-11e5-9028-67c7fcbbdc3b/cookie"}}
+    $session = $this->getSession();
+    $session->visit($this->baseUrl);
+
     return $session;
   }
 
@@ -256,7 +272,7 @@ protected function getDefaultDriverInstance() {
 
     if (is_array($this->minkDefaultDriverArgs)) {
        // Use ReflectionClass to instantiate class with received params.
-      $reflector = new ReflectionClass($this->minkDefaultDriverClass);
+      $reflector = new \ReflectionClass($this->minkDefaultDriverClass);
       $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
     }
     else {
@@ -302,6 +318,8 @@ protected function setUp() {
     $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : '';
     $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80;
 
+    $this->baseUrl = $base_url;
+
     // If the passed URL schema is 'https' then setup the $_SERVER variables
     // properly so that testing will run under HTTPS.
     if ($parsed_url['scheme'] === 'https') {
diff --git a/core/modules/simpletest/src/JavascriptTestBase.php b/core/modules/simpletest/src/JavascriptTestBase.php
new file mode 100644
index 0000000..a9c66a6
--- /dev/null
+++ b/core/modules/simpletest/src/JavascriptTestBase.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest\JavascriptTestBase.
+ */
+
+namespace Drupal\simpletest;
+
+use Zumba\Mink\Driver\PhantomJSDriver;
+
+/**
+ * Runs a browser test using phantomJS.
+ *
+ * Use this test baseclass if you want to test expected behaviour of some
+ * JavaScript.
+ */
+abstract class JavascriptTestBase extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $minkDefaultDriverClass = PhantomjsDriver::class;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $minkDefaultDriverArgs = [
+    'http://127.0.0.1:8510',
+    '/tmp/browsertestbase-templatecache',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Setup some template cache using by the phantomjs mink driver.
+    $path = '/tmp/browsertestbase-templatecache';
+    if (!file_exists($path)) {
+      mkdir($path);
+    }
+  }
+
+}
diff --git a/core/modules/simpletest/tests/src/Functional/BrowserWithJavascriptTest.php b/core/modules/simpletest/tests/src/Functional/BrowserWithJavascriptTest.php
new file mode 100644
index 0000000..6c6f0d8
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Functional/BrowserWithJavascriptTest.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\simpletest\Functional\BrowserWithJavascriptTest.
+ */
+
+namespace Drupal\Tests\simpletest\Functional;
+
+use Drupal\simpletest\BrowserTestBase;
+
+/**
+ * Tests a browser with javascript.
+ *
+ * @group javascript
+ */
+class BrowserWithJavascriptTest extends BrowserTestBase {
+
+  public function testJavascript() {
+    $this->drupalGet('<front>');
+    $session = $this->getSession();
+
+    $session->resizeWindow(400, 300);
+    $session->wait(1000, 'false');
+    $javascript = <<<JS
+    (function(){
+        var w = window,
+        d = document,
+        e = d.documentElement,
+        g = d.getElementsByTagName('body')[0],
+        x = w.innerWidth || e.clientWidth || g.clientWidth,
+        y = w.innerHeight|| e.clientHeight|| g.clientHeight;
+        var size = {};
+        size["width"]=x;
+        size["height"]= y;
+        return size;
+    }());
+JS;
+    $pageSize = $session->evaluateScript($javascript);
+    print_r($pageSize);
+    $this->assertEquals(400, $pageSize["width"]);
+    $this->assertEquals(300, $pageSize["height"]);
+  }
+
+}
diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarIntegrationTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarIntegrationTest.php
new file mode 100644
index 0000000..0e3a8ec
--- /dev/null
+++ b/core/modules/toolbar/tests/src/Functional/ToolbarIntegrationTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\toolbar\Functional\ToolbarIntegrationTest.
+ */
+
+namespace Drupal\Tests\toolbar\Functional;
+
+use Drupal\simpletest\JavascriptTestBase;
+
+/**
+ * Basic toolbar testing.
+ *
+ * @group toolbar
+ */
+class ToolbarIntegrationTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['toolbar', 'node'];
+
+  public function testToolbarToggling() {
+    $admin_user = $this->drupalCreateUser(['access toolbar', 'administer site configuration', 'access content overview']);
+    $this->drupalLogin($admin_user);
+
+    $this->drupalGet('<front>');
+    $this->assertTrue($this->getSession()->getDriver()->isVisible('.//a[@id="toolbar-link-system-admin_content"]'), 'Toolbar not open by default');
+
+    $this->getSession()->getDriver()->click('.//a[@id="toolbar-item-administration"]');
+    $this->assertFalse($this->getSession()->getDriver()->isVisible('//a[@id="toolbar-link-system-admin_content"]'), 'Toolbar not closed after clicking manage');
+  }
+
+}
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index 31169cd..cfcc530 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -10,6 +10,7 @@
     <!-- Example SIMPLETEST_BASE_URL value: http://localhost -->
     <env name="SIMPLETEST_DB" value=""/>
     <!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/databasename#table_prefix -->
+    <!-- where driver will connect to -->
   </php>
   <testsuites>
     <testsuite name="unit">
diff --git a/core/tests/README.md b/core/tests/README.md
new file mode 100644
index 0000000..df091f3
--- /dev/null
+++ b/core/tests/README.md
@@ -0,0 +1,14 @@
+# Running tests
+
+## Function tests
+
+* Start phantomjs:
+```
+phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /dev/null &
+```
+* Run the function tests:
+```
+export SIMPLETEST_DB='mysql://root@localhost/dev_d8'
+export SIMPLETEST_BASE_URL='http://d8.dev'
+vendor/bin/phpunit -c core core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
+```
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index 8d5b5ef..030a0de 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -6,6 +6,8 @@
 $baseDir = dirname($vendorDir);
 
 return array(
+    'Zumba\\Mink\\Driver\\' => array($vendorDir . '/jcalderonzumba/mink-phantomjs-driver/src'),
+    'Zumba\\GastonJS\\' => array($vendorDir . '/jcalderonzumba/gastonjs/src'),
     'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'),
     'Zend\\Hydrator\\' => array($vendorDir . '/zendframework/zend-hydrator/src'),
     'Zend\\Feed\\' => array($vendorDir . '/zendframework/zend-feed/src'),
diff --git a/vendor/jcalderonzumba/gastonjs/.travis.yml b/vendor/jcalderonzumba/gastonjs/.travis.yml
new file mode 100644
index 0000000..c64d755
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/.travis.yml
@@ -0,0 +1,37 @@
+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
+
+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 {}
diff --git a/vendor/jcalderonzumba/gastonjs/LICENSE b/vendor/jcalderonzumba/gastonjs/LICENSE
new file mode 100644
index 0000000..7ba018a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/LICENSE
@@ -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.
diff --git a/vendor/jcalderonzumba/gastonjs/README.md b/vendor/jcalderonzumba/gastonjs/README.md
new file mode 100644
index 0000000..5332619
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/README.md
@@ -0,0 +1,8 @@
+GastonJS for Webpage automation
+================================
+[![Build Status](https://travis-ci.org/jcalderonzumba/gastonjs.svg?branch=travis_ci)](https://travis-ci.org/jcalderonzumba/gastonjs)
+[![Latest Stable Version](https://poser.pugx.org/jcalderonzumba/gastonjs/v/stable)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
+[![Total Downloads](https://poser.pugx.org/jcalderonzumba/gastonjs/downloads)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
+
+
+For full documentation go to [GastonJS doc](http://gastonjs.readthedocs.org/en/latest/)
diff --git a/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh b/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh
new file mode 100755
index 0000000..426ec94
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/bin/run-tests.sh
@@ -0,0 +1,26 @@
+#!/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 src/Client/main.js 8510 1024 768 2>&1 &
+  else
+    phantomjs --ssl-protocol=any --ignore-ssl-errors=true 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
+}
+
+mkdir -p /tmp/jcalderonzumba/phantomjs
+stop_services
+start_browser_api
+CURRENT_DIR=$(pwd)
+${CURRENT_DIR}/bin/phpunit --configuration unit_tests.xml
+
diff --git a/vendor/jcalderonzumba/gastonjs/composer.json b/vendor/jcalderonzumba/gastonjs/composer.json
new file mode 100644
index 0000000..7e5d96e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/composer.json
@@ -0,0 +1,49 @@
+{
+  "name": "jcalderonzumba/gastonjs",
+  "description": "PhantomJS API based server for webpage automation",
+  "keywords": [
+    "phantomjs",
+    "headless",
+    "api",
+    "automation",
+    "browser"
+  ],
+  "homepage": "https://github.com/jcalderonzumba/gastonjs",
+  "type": "phantomjs-api",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Juan Francisco Calderón Zumba",
+      "email": "juanfcz@gmail.com",
+      "homepage": "http://github.com/jcalderonzumba"
+    }
+  ],
+  "require": {
+    "php": ">=5.4",
+    "guzzlehttp/guzzle": "~5.0|~6.0"
+  },
+  "require-dev": {
+    "symfony/process": "~2.1",
+    "symfony/phpunit-bridge": "~2.7",
+    "phpunit/phpunit": "~4.6",
+    "silex/silex": "~1.2"
+  },
+  "config": {
+    "bin-dir": "bin"
+  },
+  "autoload": {
+    "psr-4": {
+      "Zumba\\GastonJS\\": "src"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Zumba\\GastonJS\\Tests\\": "tests/unit"
+    }
+  },
+  "extra": {
+    "branch-alias": {
+      "dev-master": "1.1.x-dev"
+    }
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/command-list.md b/vendor/jcalderonzumba/gastonjs/docs/api/command-list.md
new file mode 100644
index 0000000..8a85cce
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/command-list.md
@@ -0,0 +1,81 @@
+GastonJS API browser commands
+=============================
+* [add_extension](commands/javascript/add_extension.md) --> unit test
+* [add_header](commands/headers/add_header.md)   --> unit test
+* [add_headers](commands/headers/add_headers.md) --> unit test
+* all_html
+* all_text
+* attribute
+* attributes
+* body
+* browser_error
+* [clear_cookies](commands/cookies/clear_cookies.md)  --> unit test
+* clear_network_traffic
+* [click](commands/mouse/click.md)    --> NO unit test
+* [click_coordinates](commands/mouse/click.md) --> NO unit test
+* close_window
+* [cookies](commands/cookies/cookies.md)  --> unit test
+* [cookies_enabled](commands/cookies/cookies_enabled.md)  --> unit test
+* [current_url](commands/navigation/current_url.md) --> unit test
+* debug
+* delete_text
+* disabled
+* [double_click](commands/mouse/double_click.md)    --> NO unit test
+* drag
+* equals
+* [evaluate](commands/javascript/evaluate.md) --> unit test
+* [execute](commands/javascript/execute.md) --> unit test
+* exit
+* find
+* find_within
+* [get_headers](commands/headers/get_headers.md) --> unit test
+* [go_back](commands/navigation/go_back.md)  --> unit test
+* go_forward
+* [hover](commands/mouse/hover.md)    --> NO unit test
+* key_event
+* [mouse_event](commands/mouse/mouse_event.md)  --> NO PHP implementation
+* network_traffic
+* node
+* noop
+* open_new_window
+* parents
+* pop_frame
+* push_frame
+* [reload](commands/navigation/reload.md)  --> unit test
+* remove_attribute
+* [remove_cookie](commands/cookies/remove_cookie.md)  --> unit test
+* [render](commands/render/render.md)   --> unit test
+* [render_base64](commands/render/render_base64.md)   --> unit test
+* reset
+* resize
+* [response_headers](commands/headers/response_headers.md) --> unit test
+* right_click
+* scroll_to
+* select
+* select_file
+* select_option
+* send_keys
+* set
+* set_attribute
+* [set_cookie](commands/cookies/set_cookie.md)  --> unit test
+* set_debug
+* [set_headers](commands/headers/set_headers.md)  --> unit test
+* set_http_auth
+* [set_js_errors](commands/javascript/set_js_errors.md) --> NO unit test
+* set_paper_size
+* set_url_blacklist
+* set_zoom_factor
+* source
+* status_code
+* switch_to_window
+* tag_name
+* title
+* trigger
+* value
+* visible
+* visible_text
+* [visit](commands/navigation/visit.md)  --> unit test
+* window_handle
+* window_handles
+* window_name
+* window_size
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/clear_cookies.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/clear_cookies.md
new file mode 100644
index 0000000..e872252
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/clear_cookies.md
@@ -0,0 +1,16 @@
+clear_cookies
+=========
+Command to clear all the cookies set in the browser
+##Request
+```json
+{
+    "name": "clear_cookies",
+    "args": []
+}
+```
+##Response
+```json
+{
+    "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies.md
new file mode 100644
index 0000000..77d97b2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies.md
@@ -0,0 +1,34 @@
+cookies
+=========
+Command to get the visible cookies in the current page.
+##Request
+```json
+{
+    "name": "cookies",
+    "args": []
+}
+```
+##Response
+If the page you are visiting sends cookies you will get something like:
+```json
+{
+    "response": [
+        {
+            "domain": "127.0.0.1",
+            "httponly": true,
+            "name": "b_cookie",
+            "path": "/",
+            "secure": false,
+            "value": "b_has_value"
+        },
+        {
+            "domain": "127.0.0.1",
+            "httponly": true,
+            "name": "a_cookie",
+            "path": "/",
+            "secure": false,
+            "value": "a_has_value"
+        }
+    ]
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies_enabled.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies_enabled.md
new file mode 100644
index 0000000..f7b01e0
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/cookies_enabled.md
@@ -0,0 +1,36 @@
+cookies_enabled
+===============
+Command to enable/disable the CookieJar on PhantomJS.
+##How to enable cookies
+####Request
+```json
+{
+    "name": "cookies_enabled",
+    "args": [
+        true
+    ]
+}
+```
+####Response
+```json
+{
+    "response": true
+}
+```
+
+##How to disable cookies
+####Request
+```json
+{
+    "name": "cookies_enabled",
+    "args": [
+        false
+    ]
+}
+```
+####Response
+```json
+{
+    "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/remove_cookie.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/remove_cookie.md
new file mode 100644
index 0000000..5255603
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/remove_cookie.md
@@ -0,0 +1,20 @@
+remove_cookie
+=============
+Command to delete any Cookie visible in the current page with the given name.
+
+If not found on the current page then the cookie will be search in the entire PhantomJS cookieJar so use with caution.
+##Cookie deletion request
+```json
+{
+    "name": "remove_cookie",
+    "args": [
+        "cookie_name_to_delete"
+    ]
+}
+```
+##Cookie deletion response
+```json
+{
+    "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/set_cookie.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/set_cookie.md
new file mode 100644
index 0000000..148855c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/cookies/set_cookie.md
@@ -0,0 +1,27 @@
+set_cookie
+===========
+Command to add a Cookie to PhantomJS CookieJar.
+
+Returns `true` if cookie was successfully added, `false` otherwise
+##Set a cookie request
+```json
+{
+    "name": "set_cookie",
+    "args": [
+        {
+            "name": "mycookie",
+            "value": "myvalue",
+            "path": "/",
+            "domain": "gastonjs.readthedocs.org"
+        }
+    ]
+}
+```
+##Set a cookie response
+```json
+{
+    "response": true
+}
+```
+
+For more information on how the Cookie JSON object should be check [addCookie](http://phantomjs.org/api/phantom/method/add-cookie.html) in PhantomJS documentation
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_header.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_header.md
new file mode 100644
index 0000000..56648d9
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_header.md
@@ -0,0 +1,43 @@
+add_header
+========
+This command allows you to set an additional headers for the future page requests via GastonJS.
+
+##Adding a temporal header
+This header will be valid for ONE request only, after that request it will disappear.
+```json
+{
+    "name": "add_header",
+    "args": [
+        {
+            "X-Temporal-Header": "x_temporal_value"
+        },
+        false
+    ]
+}
+```
+Response should be:
+```json
+{
+    "response": true
+}
+```
+
+##Adding a permanent header
+This header will be valid for all the requests.
+```json
+{
+    "name": "add_header",
+    "args": [
+        {
+            "X-Permanent-Test": "x_permanent_value"
+        },
+        true
+    ]
+}
+```
+Response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_headers.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_headers.md
new file mode 100644
index 0000000..52f24b9
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/add_headers.md
@@ -0,0 +1,24 @@
+add_headers
+========
+This command allows you to set the additional headers you want to send when visiting pages via GastonJS.
+
+**add_headers** will add the headers by creating the ones that do not exists and overwriting the ones that already exists.
+
+```json
+{
+    "name": "add_headers",
+    "args": [
+        {
+            "X-Header-One": "one",
+            "X-Header-Two": "two",
+            "X-Old-Header": "new-value"
+        }
+    ]
+}
+```
+Response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/get_headers.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/get_headers.md
new file mode 100644
index 0000000..ebee50e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/get_headers.md
@@ -0,0 +1,26 @@
+get_headers
+========
+This command returns an array with the additional HTTP request headers that will be sent to the server for every request issued (for pages and resources).
+
+```json
+{
+  "name":"get_headers",
+  "args":[]
+}
+```
+When there are no additional headers to be sent the response is:
+```json
+{
+  "response": []
+}
+```
+When there are additional headers to be sent the response looks like:
+```json
+{
+    "response": {
+        "X-Header-One": "one",
+        "X-Header-Three": "three",
+        "X-Header-Two": "two"
+    }
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/response_headers.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/response_headers.md
new file mode 100644
index 0000000..f253560
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/response_headers.md
@@ -0,0 +1,21 @@
+response_headers
+========
+This command returns the response headers sent from the page server when making a page request.
+
+```json
+{
+  "name":"response_headers",
+  "args":[]
+}
+```
+Response should look like:
+```json
+{
+    "response": {
+        "Host": "127.0.0.1:6789",
+        "Connection": "close",
+        "Content-Type": "text/html; charset=UTF-8",
+        "Content-Length": "671"
+    }
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/set_headers.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/set_headers.md
new file mode 100644
index 0000000..238d9d5
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/headers/set_headers.md
@@ -0,0 +1,23 @@
+set_headers
+========
+This command allows you to set the additional headers you want to send when visiting pages via GastonJS.
+
+**set_headers** WILL overwrite any other additional headers you might have set before.
+
+```json
+{
+    "name": "set_headers",
+    "args": [
+        {
+            "X-Header-One": "one",
+            "X-Header-Two": "two"
+        }
+    ]
+}
+```
+Response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/add_extension.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/add_extension.md
new file mode 100644
index 0000000..0adaf08
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/add_extension.md
@@ -0,0 +1,19 @@
+add_extension
+=============
+This command allows you to inject an external script code from the specified file into the current page.
+
+```json
+{
+    "name": "add_extension",
+    "args": [
+        "/www/web/script_extensions/extension.js"
+    ]
+}
+```
+If the script was properly injected the response should be:
+```json
+{
+    "response": "success"
+}
+```
+For more details on how this works check [PhantomJS injectJS documentation](http://phantomjs.org/api/webpage/method/inject-js.html)
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/evaluate.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/evaluate.md
new file mode 100644
index 0000000..cab8fde
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/evaluate.md
@@ -0,0 +1,29 @@
+evaluate
+========
+Command to evaluate javascript code within the current page you are browsing.
+
+##Example of use
+####Request
+```json
+{
+    "name": "evaluate",
+    "args": [
+        "(function (fibonnaciNumber) {\n  var looping = function (n) {\n    var a = 0, b = 1, f = 1;\n    for (var i = 2; i <= n; i++) {\n      f = a + b;\n      a = b;\n      b = f;\n    }\n    return f;\n  };\n  return looping(fibonnaciNumber);\n})(10);\n"
+    ]
+}
+```
+####Response
+If there are no javascript errors then the response should be the result of the evaluation of the code, in this case:
+```json
+{
+    "response": 55
+}
+```
+##Rule of thumb for javascript code
+As a recommendation try always to make your code like this:
+```javascript
+(function () {
+  //Here should go all the code you want to evaluate
+  return value;
+})();
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/execute.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/execute.md
new file mode 100644
index 0000000..63d8975
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/execute.md
@@ -0,0 +1,28 @@
+execute
+=========
+Command to execute javascript code within the current page you are browsing.
+
+##Example of use
+####Request
+```json
+{
+    "name": "execute",
+    "args": [
+        "(function () {\n  document.getElementById(\"element_1\").value = \"THIS_IS_SPARTA\";\n  document.getElementById(\"element_3\").selectedIndex = 1;\n})();\n"
+    ]
+}
+```
+####Response
+If there are no javascript errors then the response should be:
+```json
+{
+    "response": true
+}
+```
+##Rule of thumb for javascript code
+As a recommendation try always to make your code like this:
+```javascript
+(function () {
+  //Here should go all the code you want to execute
+})();
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/set_js_errors.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/set_js_errors.md
new file mode 100644
index 0000000..8a3c276
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/javascript/set_js_errors.md
@@ -0,0 +1,41 @@
+set_js_errors
+=============
+This command allows you to enable/disable javascript error control when executing commands.
+
+When javascript error control is enabled all commands where the page has javascript errors will fail.
+
+This is a good practice so you can detect any javascript errors on your pages.
+
+
+##Enable javascript error control
+####Request
+```json
+{
+    "name": "set_js_errors",
+    "args": [
+        true
+    ]
+}
+```
+####Response
+```json
+{
+    "response": true
+}
+```
+##Disable javascript error control
+####Request
+```json
+{
+    "name": "set_js_errors",
+    "args": [
+        false
+    ]
+}
+```
+####Response
+```json
+{
+    "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click.md
new file mode 100644
index 0000000..a3c8fe3
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click.md
@@ -0,0 +1,30 @@
+click
+========
+Sends a click event to the browser, this event is sent as if it comes as part of the user interaction, meaning is not a synthetic [DOM event](http://www.w3.org/TR/DOM-Level-2-Events/events.html).
+
+In order to send a click you have to send the page and the element id where you want to click.
+
+**TODO: add link to find command documentation**
+
+##Click Request
+```json
+{
+    "name": "click",
+    "args": [1, 0]
+}
+```
+A successful click command has the following response:
+##Click Response
+```json
+{
+    "response": {
+        "position": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the click was done.
+
+You need coordinates to click because that is how PhantomJS works, for more info check [PhantomJS native events](http://phantomjs.org/api/webpage/method/send-event.html).
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click_coordinates.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click_coordinates.md
new file mode 100644
index 0000000..04f05e4
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/click_coordinates.md
@@ -0,0 +1,28 @@
+
+
+click_coordinates
+========
+Sends a click_coordinates event to the browser, this event is sent as if it comes as part of the user interaction, meaning is not a synthetic [DOM event](http://www.w3.org/TR/DOM-Level-2-Events/events.html).
+
+This is a low level command as you have to now beforehand the **X,Y** coordinates of the element you want to click. **Use it with caution.**
+
+##Command Request
+```json
+{
+    "name": "click_coordinates",
+    "args": [165, 95]
+}
+```
+A successful click_coordinates command has the following response:
+##Command Response
+```json
+{
+    "response": {
+        "click": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the click_coordinates was done.
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/double_click.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/double_click.md
new file mode 100644
index 0000000..378c70e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/double_click.md
@@ -0,0 +1,30 @@
+double_click
+========
+Sends a double_click event to the browser, this event is sent as if it comes as part of the user interaction, meaning is not a synthetic [DOM event](http://www.w3.org/TR/DOM-Level-2-Events/events.html).
+
+In order to send a click you have to send the page and the element id where you want to click.
+
+**TODO: add link to find command documentation**
+
+##Command Request
+```json
+{
+    "name": "double_click",
+    "args": [1, 0]
+}
+```
+A successful double_click command has the following response:
+##Command Response
+```json
+{
+    "response": {
+        "position": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the double_click was done.
+
+You need coordinates to click because that is how PhantomJS works, for more info check [PhantomJS native events](http://phantomjs.org/api/webpage/method/send-event.html).
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/hover.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/hover.md
new file mode 100644
index 0000000..e76ad56
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/hover.md
@@ -0,0 +1,28 @@
+hover
+========
+Hover command is basically a command to move the mouse to the page and element set by the command arguments.
+
+**TODO: add link to find command documentation**
+
+##Command Request
+```json
+{
+    "name": "hover",
+    "args": [1, 0]
+}
+```
+A successful hover or mouse move command has the following response:
+##Command Response
+```json
+{
+    "response": {
+        "position": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the mouse has been positioned.
+
+You need coordinates to click because that is how PhantomJS works, for more info check [PhantomJS native events](http://phantomjs.org/api/webpage/method/send-event.html).
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/mouse_event.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/mouse_event.md
new file mode 100644
index 0000000..6ae5110
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/mouse_event.md
@@ -0,0 +1,37 @@
+mouse_event
+===========
+
+This command allows you to send a mouse event to PhantomJS on a given page and element.
+
+Allowed mouse events are:
+
+* mouseup
+* mousedown
+* mousemove
+* doubleclick
+* click
+
+**TODO: add link to find command documentation**
+
+##Command Request
+```json
+{
+    "name": "mouse_event",
+    "args": [1, 0, "mousemove"]
+}
+```
+A successful mouse_event command has the following response:
+##Command Response
+```json
+{
+    "response": {
+        "position": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the mouse_event was done.
+
+You need coordinates to click because that is how PhantomJS works, for more info check [PhantomJS native events](http://phantomjs.org/api/webpage/method/send-event.html).
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/right_click.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/right_click.md
new file mode 100644
index 0000000..c26b2c1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/mouse/right_click.md
@@ -0,0 +1,30 @@
+right_click
+========
+Sends a right_click event to the browser, this event is sent as if it comes as part of the user interaction, meaning is not a synthetic [DOM event](http://www.w3.org/TR/DOM-Level-2-Events/events.html).
+
+In order to send a click you have to send the page and the element id where you want to click.
+
+**TODO: add link to find command documentation**
+
+##Command Request
+```json
+{
+    "name": "right_click",
+    "args": [1, 0]
+}
+```
+A successful right_click command has the following response:
+##Command Response
+```json
+{
+    "response": {
+        "position": {
+            "x": 165,
+            "y": 59
+        }
+    }
+}
+```
+Where **x** and **y** are the coordinates where the right_click was done.
+
+You need coordinates to click because that is how PhantomJS works, for more info check [PhantomJS native events](http://phantomjs.org/api/webpage/method/send-event.html).
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/current_url.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/current_url.md
new file mode 100644
index 0000000..7e9edb5
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/current_url.md
@@ -0,0 +1,16 @@
+current-url
+===================
+
+To get the current url in the browser, send:
+```json
+{
+  "name":"current_url",
+  "args":[]
+}
+```
+Response should be:
+```json
+{
+  "response":"http://gastonjs.readthedocs.org/en/latest/"
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_back.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_back.md
new file mode 100644
index 0000000..2de61a0
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_back.md
@@ -0,0 +1,16 @@
+go_back
+========
+This command allows you (if possible) to go back, loading the previous page in your browser history.
+
+```json
+{
+  "name":"go_back",
+  "args":[]
+}
+```
+If possible to go back then response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_forward.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_forward.md
new file mode 100644
index 0000000..81334b0
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/go_forward.md
@@ -0,0 +1,15 @@
+go_forward
+========
+This command allows you (if possible) to go forward, loading the next page in your browser history.
+```json
+{
+  "name":"go_forward",
+  "args":[]
+}
+```
+If possible to go forward then response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/reload.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/reload.md
new file mode 100644
index 0000000..f7ce65c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/reload.md
@@ -0,0 +1,15 @@
+reload
+========
+This command allows you to reload the page you are currently in, to reload send:
+```json
+{
+  "name":"reload",
+  "args":[]
+}
+```
+Response should be:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/visit.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/visit.md
new file mode 100644
index 0000000..3f59f58
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/navigation/visit.md
@@ -0,0 +1,23 @@
+visit
+============
+To visit a page you have to send the following JSON POST request body:
+
+```json
+{
+  "name": "visit",
+  "args":[
+    "http://gastonjs.readthedocs.org/en/latest/"
+  ]
+}
+```
+
+GastonJS takes as `"visit"` as the command to run and expects only one argument which is the page you want to visit, in this case is `"http://gastonjs.readthedocs.org/en/latest/"`
+
+A successful `"visit"` command will return the following body:
+```json
+{
+  "response": {
+      "status": "success"
+  }
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render.md
new file mode 100644
index 0000000..5493462
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render.md
@@ -0,0 +1,45 @@
+render
+========
+This command allows you render a web page to an image buffer and saves it as the specified filename given in the command arguments.
+
+The image format will be automatically set based on the filename extension, the current supported formats are:
+* PNG
+* GIF
+* JPEG
+* PDF
+
+##Commmand arguments:
+  1. path
+    * The path where you want the file to be saved
+  2. full
+    * `true` for rendering all the page, `false` if we are going to use a selection
+  3. selector
+    * If full is `false` then you have to specify the CSS selection you want to render, internally we will use document.querySelector.
+
+##Full page render request:
+```json
+{
+    "name" : "render",
+    "args": [
+      "/path/to/the/file.png", true, null
+    ]
+}
+```
+##Part of a page render request:
+```json
+{
+    "name" : "render",
+    "args": [
+      "/path/to/the/file.png", false, "body > div.wrapper > div.main.clearfix"
+    ]
+}
+
+```
+
+##Response:
+A successful render command will reply with:
+```json
+{
+  "response": true
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render_base64.md b/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render_base64.md
new file mode 100644
index 0000000..fe87990
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/commands/render/render_base64.md
@@ -0,0 +1,44 @@
+render_base64
+=============
+This command allows you render a web page to an image buffer and returns the base64 encoded representation of the image.
+
+The supported formats are:
+
+* PNG
+* GIF
+* JPEG
+
+##Commmand arguments:
+  1. image_format
+  2. full
+    * `true` for rendering all the page, `false` if we are going to use a selection
+  3. selector
+    * If full is `false` then you have to specify the CSS selection you want to render, internally we will use document.querySelector.
+
+##Full page render request:
+```json
+{
+    "name" : "render_base64",
+    "args": [
+      "png", true, null
+    ]
+}
+```
+##Part of a page render request:
+```json
+{
+    "name" : "render_base64",
+    "args": [
+      "png", false, "body > div.wrapper > div.main.clearfix"
+    ]
+}
+
+```
+
+##Response:
+A successful render_base64 command will reply with:
+```json
+{
+  "response": "iVBORw0KGgoAAAANSUhEUgAABAAAAACYCAYAAAB6Z5u+AAAABHNCSVQICAgIfAh......"
+}
+```
diff --git a/vendor/jcalderonzumba/gastonjs/docs/api/index.md b/vendor/jcalderonzumba/gastonjs/docs/api/index.md
new file mode 100644
index 0000000..6a4aacb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/api/index.md
@@ -0,0 +1,64 @@
+GastonJS HTTP API
+==================
+Lets start by saying that the API is **not REST**, is a simple HTTP interface where you can send POST requests with the command you want the browser to execute and the parameters to execute such commands.
+
+This statement can change in the future but that will require all clients to upgrade to the REST implementation.
+
+##Start the Browser and the API
+```bash
+phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /tmp/gastonjs.log &
+```
+This will start a phantomjs process and the API listening on the 8510 port, the 1024x768 parameters are the width and height you want the browser to use. You can start the API on the port you want 8510 is just an example.
+
+##API endpoint
+Your client can start making HTTP POST requests to `http://localhost:8510/v1/api`
+
+##API command request
+Every POST request to the API needs a command name and the arguments that the commands needs to run.
+
+This command is a JSON body that has the following schema:
+```json
+{
+  "name": "COMMAND_NAME",
+  "args" : [
+    "COMMAND_ARG_1",
+    "COMMAND_ARG_2",
+    ...
+  ]
+}
+```
+
+##API response
+* Successful command execution has an **HTTP 200 status code** and a body:
+```json
+{
+  "response":{
+    OBJECT_DEPENDS_ON_THE_COMMAND
+    }
+}
+```
+* Error while executing command has an **HTTP 500 status code** and a body:
+```json
+{
+  "error": {
+      "name": "GastonJSExceptionClass",
+      "args": "ExceptionClassArguments"
+    }
+}
+```
+
+##API request example
+The following example will teach you how to visit a page and save the rendered page:
+
+1. Visit the page:
+```bash
+curl -X POST -H "Content-Type: application/json" -d '{"name":"visit","args":["https://www.google.es"]}' 'http://127.0.0.1:8510/v1/api'
+```
+
+2. Save the rendered page to a PNG file:
+```bash
+curl -X POST -H "Content-Type: application/json" -d '{"name":"render","args":["/tmp/google.png", true]}' 'http://127.0.0.1:8510/v1/api'
+```
+
+##Full API command documentation
+* [API commands list](command-list.md)
diff --git a/vendor/jcalderonzumba/gastonjs/docs/clients/php/index.md b/vendor/jcalderonzumba/gastonjs/docs/clients/php/index.md
new file mode 100644
index 0000000..7888d37
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/clients/php/index.md
@@ -0,0 +1,3 @@
+PHP GastonJS Client
+====================
+TODO.
diff --git a/vendor/jcalderonzumba/gastonjs/docs/index.md b/vendor/jcalderonzumba/gastonjs/docs/index.md
new file mode 100644
index 0000000..0c23462
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/docs/index.md
@@ -0,0 +1,29 @@
+# Welcome to GastonJS documentation
+Creating web pages and ensuring that they behave as you want them to behave is something that a lot of projects try to solve, so let me explain you what this project is about:
+
+* GastonJS allows you to use all the power [PhantomJS](http://phantomjs.org/) provides by implementing a simple command based HTTP API.
+* GastonJS API allows you to implement a browser client on any programming language that can make HTTP requests.
+* GastonJS gives you all the control, you are the one that will tell the browser what to do, similar to Selenium based tests.
+
+## Installation
+
+GastonJS comes with a PHP built in client that allows you to control the browser and interact with it.
+
+The objective of this document is not to teach you how to install [PhantomJS](http://phantomjs.org/) so please make sure you have a working installation of [PhantomJS](http://phantomjs.org/) before using GastonJS.
+
+The recommended way to install GastonJS and the PHP built in client is through [Composer](https://getcomposer.org/):
+
+```bash
+composer require jcalderonzumba/gastonjs
+```
+
+Everything will be installed inside `vendor` folder, as with any composer package you can start using it by including the autoloading script in your PHP project.
+
+## Learn to control the Browser
+* [GastonJS API](api/index.md)
+* [PHP GastonJS Client](clients/php/index.md)
+
+## Special thanks
+None of this work would have been possible without the awesome work done by the [Poltergeist team](https://github.com/teampoltergeist/poltergeist).
+
+We fork their code and took it to another level, but the roots are still there and we want to acknowledge that.
diff --git a/vendor/jcalderonzumba/gastonjs/examples/go/main.go b/vendor/jcalderonzumba/gastonjs/examples/go/main.go
new file mode 100644
index 0000000..d50e6d2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/examples/go/main.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+)
+
+func main() {
+	url := "http://127.0.0.1:8510/api/v1"
+	fmt.Println("URL:>", url)
+
+	var commandStr = []byte(`{"name": "visit", "args": ["http://www.google.es"]}`)
+
+  req, err := http.NewRequest("POST", url, bytes.NewBuffer(commandStr))
+	req.Header.Set("Content-Type", "application/json")
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		panic(err)
+	}
+
+  responseStr, err := ioutil.ReadAll(resp.Body)
+  fmt.Printf("%s\n", responseStr)
+
+	defer resp.Body.Close()
+
+  commandStr = []byte(`{"name": "render", "args": ["/Users/juan/Downloads/page_image.png", true, null]}`)
+  renderReq, renderErr := http.NewRequest("POST", url, bytes.NewBuffer(commandStr))
+	renderReq.Header.Set("Content-Type", "application/json")
+
+	renderResp, renderErr := client.Do(renderReq)
+	if renderErr != nil {
+		panic(renderErr)
+	}
+
+  renderResponseStr, renderErr := ioutil.ReadAll(renderResp.Body)
+  fmt.Printf("%s\n", renderResponseStr)
+
+	defer renderResp.Body.Close()
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/examples/java/GastonJSClient.java b/vendor/jcalderonzumba/gastonjs/examples/java/GastonJSClient.java
new file mode 100644
index 0000000..c923d70
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/examples/java/GastonJSClient.java
@@ -0,0 +1,48 @@
+import java.io.InputStream;
+import java.lang.System;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+public class GastonJSClient{
+
+    public static void main(String[] args) throws Exception{
+            // write your code here
+            String visitPage = "{\n" +
+                    "  \"name\": \"visit\",\n" +
+                    "  \"args\":[\n" +
+                    "    \"http://www.google.es\"\n" +
+                    "  ]\n" +
+                    "}";
+            String renderPage = "{\"name\":\"render\",\"args\":[\"/Users/juan/Downloads/page_image.png\",true,null]}";
+            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+            try {
+                //Do the visit
+                HttpPost request = new HttpPost("http://127.0.0.1:8510/v1/api");
+                StringEntity params = new StringEntity(visitPage);
+                request.addHeader("content-type", "application/json");
+                request.setEntity(params);
+                HttpResponse response = httpClient.execute(request);
+                InputStream body = response.getEntity().getContent();
+                String myString = IOUtils.toString(body, "UTF-8");
+                System.out.println(myString);
+                //Do the page print
+                params = new StringEntity(renderPage);
+                request.setEntity(params);
+                response = httpClient.execute(request);
+                body = response.getEntity().getContent();
+                myString = IOUtils.toString(body, "UTF-8");
+                System.out.println(myString);
+                // handle response here...
+            } catch (Exception ex) {
+                // handle exception here
+                System.out.println(ex.toString());
+            } finally {
+                httpClient.close();
+            }
+        }
+}
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/examples/nodejs/main.js b/vendor/jcalderonzumba/gastonjs/examples/nodejs/main.js
new file mode 100644
index 0000000..1ba7e56
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/examples/nodejs/main.js
@@ -0,0 +1,37 @@
+"use strict";
+var http = require("http");
+
+var visitCommand = JSON.stringify({name: 'visit', args: ['http://www.google.es']});
+var renderCommand = JSON.stringify({name: 'render', args: ['/Users/juan/Downloads/page_image.png', true, null]});
+
+var postOptions = {
+  host: '127.0.0.1',
+  port : 8510,
+  path: '/api/v1',
+  method: 'POST',
+  headers: {
+    'Content-type': 'application/json',
+    'Content-Length': visitCommand.length
+  }
+};
+
+var postRequest = http.request(postOptions, function(res){
+  res.setEncoding('utf8');
+  res.on('data', function(chunk){
+    console.log(chunk);
+  });
+});
+
+postRequest.write(visitCommand);
+postRequest.end();
+
+postOptions.headers['Content-Length']=renderCommand.length;
+postRequest = http.request(postOptions, function(res){
+  res.setEncoding('utf8');
+  res.on('data', function(chunk){
+    console.log(chunk);
+  });
+});
+
+postRequest.write(renderCommand);
+postRequest.end();
diff --git a/vendor/jcalderonzumba/gastonjs/examples/python/main.py b/vendor/jcalderonzumba/gastonjs/examples/python/main.py
new file mode 100644
index 0000000..53c8f19
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/examples/python/main.py
@@ -0,0 +1,18 @@
+import httplib
+import json
+
+__author__ = 'juan'
+
+connection = httplib.HTTPConnection('127.0.0.1', 8510)
+headers = {'Content-type': 'application/json'}
+command = {'name': 'visit', 'args': ['http://www.google.es']}
+
+jsonCommand = json.dumps(command)
+connection.request('POST', '/v1/api', jsonCommand, headers)
+response = connection.getresponse()
+print(response.read().decode())
+command = {'name': 'render', 'args': ['/Users/juan/Downloads/page_image.png', True, None]}
+jsonCommand = json.dumps(command)
+connection.request('POST', '/v1/api', jsonCommand, headers)
+response = connection.getresponse()
+print(response.read().decode())
diff --git a/vendor/jcalderonzumba/gastonjs/mkdocs.yml b/vendor/jcalderonzumba/gastonjs/mkdocs.yml
new file mode 100644
index 0000000..51d881f
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/mkdocs.yml
@@ -0,0 +1,40 @@
+site_name: GastonJS Documentation
+pages:
+  - GastonJS introduction: index.md
+  - GastonJS API :
+    - 'Introduction': api/index.md
+    - 'Available commands': api/command-list.md
+    - 'Navigation commands':
+      - 'visit': api/commands/navigation/visit.md
+      - 'current_url': api/commands/navigation/current_url.md
+      - 'reload': api/commands/navigation/reload.md
+      - 'go_back': api/commands/navigation/go_back.md
+      - 'go_forward': api/commands/navigation/go_forward.md
+    - 'Header commands' :
+      - 'get_headers': api/commands/headers/get_headers.md
+      - 'response_headers': api/commands/headers/response_headers.md
+      - 'set_headers': api/commands/headers/set_headers.md
+      - 'add_headers': api/commands/headers/add_headers.md
+      - 'add_header': api/commands/headers/add_header.md
+    - 'Javascript commands' :
+      - 'add_extension': api/commands/javascript/add_extension.md
+      - 'execute': api/commands/javascript/execute.md
+      - 'evaluate': api/commands/javascript/evaluate.md
+      - 'set_js_errors': api/commands/javascript/set_js_errors.md
+    - 'Cookies commands' :
+      - 'cookies': api/commands/cookies/cookies.md
+      - 'clear_cookies': api/commands/cookies/clear_cookies.md
+      - 'cookies_enabled': api/commands/cookies/cookies_enabled.md
+      - 'remove_cookie': api/commands/cookies/remove_cookie.md
+      - 'set_cookie': api/commands/cookies/set_cookie.md
+    - 'Mouse commands':
+        - 'click': api/commands/mouse/click.md
+        - 'right_click': api/commands/mouse/right_click.md
+        - 'hover': api/commands/mouse/hover.md
+        - 'double_click': api/commands/mouse/double_click.md
+        - 'click_coordinates': api/commands/mouse/click_coordinates.md
+        - 'mouse_event': api/commands/mouse/mouse_event.md
+    - 'Render commands':
+        - 'render': api/commands/render/render.md
+        - 'render_base64': api/commands/render/render_base64.md
+  - GastonJS PHP client: clients/php/index.md
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php b/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php
new file mode 100644
index 0000000..5c2a337
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/Browser.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Class Browser
+ * @package Zumba\GastonJS
+ */
+class Browser extends BrowserBase {
+
+  use BrowserAuthenticationTrait;
+  use BrowserConfigurationTrait;
+  use BrowserCookieTrait;
+  use BrowserFileTrait;
+  use BrowserFrameTrait;
+  use BrowserHeadersTrait;
+  use BrowserMouseEventTrait;
+  use BrowserNavigateTrait;
+  use BrowserNetworkTrait;
+  use BrowserPageElementTrait;
+  use BrowserPageTrait;
+  use BrowserRenderTrait;
+  use BrowserScriptTrait;
+  use BrowserWindowTrait;
+
+  /**
+   * @param string $phantomJSHost
+   * @param mixed  $logger
+   */
+  public function __construct($phantomJSHost, $logger = null) {
+    $this->phantomJSHost = $phantomJSHost;
+    $this->logger = $logger;
+    $this->debug = false;
+    $this->createApiClient();
+  }
+
+  /**
+   * Returns the value of a given element in a page
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function value($pageId, $elementId) {
+    return $this->command('value', $pageId, $elementId);
+  }
+
+  /**
+   * Sets a value to a given element in a given page
+   * @param $pageId
+   * @param $elementId
+   * @param $value
+   * @return mixed
+   */
+  public function set($pageId, $elementId, $value) {
+    return $this->command('set', $pageId, $elementId, $value);
+  }
+
+  /**
+   * Tells whether an element on a page is visible or not
+   * @param $pageId
+   * @param $elementId
+   * @return bool
+   */
+  public function isVisible($pageId, $elementId) {
+    return $this->command('visible', $pageId, $elementId);
+  }
+
+  /**
+   * @param $pageId
+   * @param $elementId
+   * @return bool
+   */
+  public function isDisabled($pageId, $elementId) {
+    return $this->command('disabled', $pageId, $elementId);
+  }
+
+  /**
+   * Drag an element to a another in a given page
+   * @param $pageId
+   * @param $fromId
+   * @param $toId
+   * @return mixed
+   */
+  public function drag($pageId, $fromId, $toId) {
+    return $this->command('drag', $pageId, $fromId, $toId);
+  }
+
+  /**
+   * Selects a value in the given element and page
+   * @param $pageId
+   * @param $elementId
+   * @param $value
+   * @return mixed
+   */
+  public function select($pageId, $elementId, $value) {
+    return $this->command('select', $pageId, $elementId, $value);
+  }
+
+  /**
+   * Triggers an event to a given element on the given page
+   * @param $pageId
+   * @param $elementId
+   * @param $event
+   * @return mixed
+   */
+  public function trigger($pageId, $elementId, $event) {
+    return $this->command('trigger', $pageId, $elementId, $event);
+  }
+
+  /**
+   * TODO: not sure what this does, needs to do normalizeKeys
+   * @param int   $pageId
+   * @param int   $elementId
+   * @param array $keys
+   * @return mixed
+   */
+  public function sendKeys($pageId, $elementId, $keys) {
+    return $this->command('send_keys', $pageId, $elementId, $this->normalizeKeys($keys));
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php
new file mode 100644
index 0000000..7416a76
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserAuthenticationTrait.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserAuthenticationTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserAuthenticationTrait {
+  /**
+   * Sets basic HTTP authentication
+   * @param $user
+   * @param $password
+   * @return bool
+   */
+  public function setHttpAuth($user, $password) {
+    return $this->command('set_http_auth', $user, $password);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php
new file mode 100644
index 0000000..3ef14d0
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserBase.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+use Zumba\GastonJS\Exception\BrowserError;
+use Zumba\GastonJS\Exception\DeadClient;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\ConnectException;
+use GuzzleHttp\Exception\ServerException;
+
+/**
+ * Class BrowserBase
+ * @package Zumba\GastonJS\Browser
+ */
+class BrowserBase {
+  /** @var mixed */
+  protected $logger;
+  /** @var  bool */
+  protected $debug;
+  /** @var  string */
+  protected $phantomJSHost;
+  /** @var  Client */
+  protected $apiClient;
+
+  /**
+   *  Creates an http client to consume the phantomjs API
+   */
+  protected function createApiClient() {
+    // Provide a BC switch between guzzle 5 and guzzle 6.
+    if (class_exists('GuzzleHttp\Psr7\Response')) {
+      $this->apiClient = new Client(array("base_uri" => $this->getPhantomJSHost()));
+    }
+    else {
+      $this->apiClient = new Client(array("base_url" => $this->getPhantomJSHost()));
+    }
+  }
+
+  /**
+   * TODO: not sure how to do the normalizeKeys stuff fix when needed
+   * @param $keys
+   * @return mixed
+   */
+  protected function normalizeKeys($keys) {
+    return $keys;
+  }
+
+  /**
+   * @return Client
+   */
+  public function getApiClient() {
+    return $this->apiClient;
+  }
+
+  /**
+   * @return string
+   */
+  public function getPhantomJSHost() {
+    return $this->phantomJSHost;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getLogger() {
+    return $this->logger;
+  }
+
+  /**
+   * Restarts the browser
+   */
+  public function restart() {
+    //TODO: Do we really need to do this?, we are just a client
+  }
+
+  /**
+   * Sends a command to the browser
+   * @throws BrowserError
+   * @throws \Exception
+   * @return mixed
+   */
+  public function command() {
+    try {
+      $args = func_get_args();
+      $commandName = $args[0];
+      array_shift($args);
+      $messageToSend = json_encode(array('name' => $commandName, 'args' => $args));
+      /** @var $commandResponse \GuzzleHttp\Psr7\Response|\GuzzleHttp\Message\Response */
+      $commandResponse = $this->getApiClient()->post("/api", array("body" => $messageToSend));
+      $jsonResponse = json_decode($commandResponse->getBody(), TRUE);
+    } catch (ServerException $e) {
+      $jsonResponse = json_decode($e->getResponse()->getBody()->getContents(), true);
+    } catch (ConnectException $e) {
+      throw new DeadClient($e->getMessage(), $e->getCode(), $e);
+    } catch (\Exception $e) {
+      throw $e;
+    }
+
+    if (isset($jsonResponse['error'])) {
+      throw $this->getErrorClass($jsonResponse);
+    }
+
+    return $jsonResponse['response'];
+  }
+
+  /**
+   * @param $error
+   * @return BrowserError
+   */
+  protected function getErrorClass($error) {
+    $errorClassMap = array(
+      'Poltergeist.JavascriptError'   => "Zumba\\GastonJS\\Exception\\JavascriptError",
+      'Poltergeist.FrameNotFound'     => "Zumba\\GastonJS\\Exception\\FrameNotFound",
+      'Poltergeist.InvalidSelector'   => "Zumba\\GastonJS\\Exception\\InvalidSelector",
+      'Poltergeist.StatusFailError'   => "Zumba\\GastonJS\\Exception\\StatusFailError",
+      'Poltergeist.NoSuchWindowError' => "Zumba\\GastonJS\\Exception\\NoSuchWindowError",
+      'Poltergeist.ObsoleteNode'      => "Zumba\\GastonJS\\Exception\\ObsoleteNode"
+    );
+    if (isset($error['error']['name']) && isset($errorClassMap[$error["error"]["name"]])) {
+      return new $errorClassMap[$error["error"]["name"]]($error);
+    }
+
+    return new BrowserError($error);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php
new file mode 100644
index 0000000..0db7f07
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserConfigurationTrait.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+
+/**
+ * Trait BrowserConfigurationTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserConfigurationTrait {
+  /**
+   * Set whether to fail or not on javascript errors found on the page
+   * @param bool $enabled
+   * @return bool
+   */
+  public function jsErrors($enabled = true) {
+    return $this->command('set_js_errors', $enabled);
+  }
+
+  /**
+   * Set a blacklist of urls that we are not supposed to load
+   * @param array $blackList
+   * @return bool
+   */
+  public function urlBlacklist($blackList) {
+    return $this->command('set_url_blacklist', $blackList);
+  }
+
+  /**
+   * Set the debug mode on the browser
+   * @param bool $enable
+   * @return bool
+   */
+  public function debug($enable = false) {
+    $this->debug = $enable;
+    return $this->command('set_debug', $this->debug);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php
new file mode 100644
index 0000000..2b693c0
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserCookieTrait.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+use Zumba\GastonJS\Cookie;
+
+/**
+ * Trait BrowserCookieTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserCookieTrait {
+  /**
+   * Gets the cookies on the browser
+   * @return array
+   */
+  public function cookies() {
+    $cookies = $this->command('cookies');
+    $objCookies = array();
+    foreach ($cookies as $cookie) {
+      $objCookies[$cookie["name"]] = new Cookie($cookie);
+    }
+    return $objCookies;
+  }
+
+  /**
+   * Sets a cookie on the browser, expires times is set in seconds
+   * @param $cookie
+   * @return mixed
+   */
+  public function setCookie($cookie) {
+    //TODO: add error control when the cookie array is not valid
+    if (isset($cookie["expires"])) {
+      $cookie["expires"] = intval($cookie["expires"]) * 1000;
+    }
+    return $this->command('set_cookie', $cookie);
+  }
+
+  /**
+   * Deletes a cookie on the browser if exists
+   * @param $cookieName
+   * @return bool
+   */
+  public function removeCookie($cookieName) {
+    return $this->command('remove_cookie', $cookieName);
+  }
+
+  /**
+   * Clear all the cookies
+   * @return bool
+   */
+  public function clearCookies() {
+    return $this->command('clear_cookies');
+  }
+
+  /**
+   * Enables or disables the cookies con phantomjs
+   * @param bool $enabled
+   * @return bool
+   */
+  public function cookiesEnabled($enabled = true) {
+    return $this->command('cookies_enabled', $enabled);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php
new file mode 100644
index 0000000..51fc745
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFileTrait.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserFileTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserFileTrait {
+  /**
+   * Selects a file to send to the browser to a given page
+   * @param $pageId
+   * @param $elementId
+   * @param $value
+   * @return mixed
+   */
+  public function selectFile($pageId, $elementId, $value) {
+    return $this->command('select_file', $pageId, $elementId, $value);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php
new file mode 100644
index 0000000..edefe5e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserFrameTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserFrameTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserFrameTrait {
+  /**
+   * Back to the parent of the iframe if possible
+   * @return mixed
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function popFrame() {
+    return $this->command("pop_frame");
+  }
+
+  /**
+   * Goes into the iframe to do stuff
+   * @param string $name
+   * @param int    $timeout
+   * @return mixed
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function pushFrame($name, $timeout = null) {
+    return $this->command("push_frame", $name, $timeout);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php
new file mode 100644
index 0000000..8300048
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserHeadersTrait.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserHeadersTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserHeadersTrait {
+  /**
+   * Returns the headers of the current page that will be used the next request
+   * @return mixed
+   */
+  public function getHeaders() {
+    return $this->command('get_headers');
+  }
+
+  /**
+   * Given an array of headers, set such headers for the requests, removing all others
+   * @param array $headers
+   * @return mixed
+   */
+  public function setHeaders($headers) {
+    return $this->command('set_headers', $headers);
+  }
+
+  /**
+   * Adds headers to current page overriding the existing ones for the next requests
+   * @param $headers
+   * @return mixed
+   */
+  public function addHeaders($headers) {
+    return $this->command('add_headers', $headers);
+  }
+
+  /**
+   * Adds a header to the page making it permanent if needed
+   * @param $header
+   * @param $permanent
+   * @return mixed
+   */
+  public function addHeader($header, $permanent = false) {
+    return $this->command('add_header', $header, $permanent);
+  }
+
+  /**
+   * Gets the response headers after a request
+   * @return mixed
+   */
+  public function responseHeaders() {
+    return $this->command('response_headers');
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php
new file mode 100644
index 0000000..38ec5a6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserMouseEventTrait.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserMouseEventTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserMouseEventTrait {
+  /**
+   * Click on a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function click($pageId, $elementId) {
+    return $this->command('click', $pageId, $elementId);
+  }
+
+  /**
+   * Triggers a right click on a page an element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function rightClick($pageId, $elementId) {
+    return $this->command('right_click', $pageId, $elementId);
+  }
+
+  /**
+   * Triggers a double click in a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function doubleClick($pageId, $elementId) {
+    return $this->command('double_click', $pageId, $elementId);
+  }
+
+  /**
+   * Hovers over an element in a given page
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function hover($pageId, $elementId) {
+    return $this->command('hover', $pageId, $elementId);
+  }
+
+  /**
+   * Click on given coordinates, THIS DOES NOT depend on the page, it just clicks on where we are right now
+   * @param $coordX
+   * @param $coordY
+   * @return mixed
+   */
+  public function clickCoordinates($coordX, $coordY) {
+    return $this->command('click_coordinates', $coordX, $coordY);
+  }
+
+  /**
+   * Scrolls the page by a given left and top coordinates
+   * @param $left
+   * @param $top
+   * @return mixed
+   */
+  public function scrollTo($left, $top) {
+    return $this->command('scroll_to', $left, $top);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php
new file mode 100644
index 0000000..24189af
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNavigateTrait.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+use Zumba\GastonJS\Exception\BrowserError;
+
+/**
+ * Trait BrowserNavigateTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserNavigateTrait {
+
+  /**
+   * Send a visit command to the browser
+   * @param $url
+   * @return mixed
+   */
+  public function visit($url) {
+    return $this->command('visit', $url);
+  }
+
+  /**
+   * Gets the current url we are in
+   * @return mixed
+   */
+  public function currentUrl() {
+    return $this->command('current_url');
+  }
+
+  /**
+   * Goes back on the browser history if possible
+   * @return bool
+   * @throws BrowserError
+   * @throws \Exception
+   */
+  public function goBack() {
+    return $this->command('go_back');
+  }
+
+  /**
+   * Goes forward on the browser history if possible
+   * @return mixed
+   * @throws BrowserError
+   * @throws \Exception
+   */
+  public function goForward() {
+    return $this->command('go_forward');
+  }
+
+  /**
+   * Reloads the current page we are in
+   */
+  public function reload() {
+    return $this->command('reload');
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php
new file mode 100644
index 0000000..d79d21e
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserNetworkTrait.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+use Zumba\GastonJS\NetworkTraffic\Request;
+
+/**
+ * Trait BrowserNetworkTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserNetworkTrait {
+  /**
+   * Get all the network traffic that the page have created
+   * @return array
+   */
+  public function networkTraffic() {
+    $networkTraffic = $this->command('network_traffic');
+    $requestTraffic = array();
+
+    if (count($networkTraffic) === 0) {
+      return null;
+    }
+
+    foreach ($networkTraffic as $traffic) {
+      $requestTraffic[] = new Request($traffic["request"], $traffic["responseParts"]);
+    }
+
+    return $requestTraffic;
+  }
+
+  /**
+   * Clear the network traffic data stored on the phantomjs code
+   * @return mixed
+   */
+  public function clearNetworkTraffic() {
+    return $this->command('clear_network_traffic');
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php
new file mode 100644
index 0000000..3f998fa
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageElementTrait.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserPageElementTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserPageElementTrait {
+  /**
+   * Find elements given a method and a selector
+   * @param $method
+   * @param $selector
+   * @return array
+   */
+  public function find($method, $selector) {
+    $result = $this->command('find', $method, $selector);
+    $found["page_id"] = $result["page_id"];
+    foreach ($result["ids"] as $id) {
+      $found["ids"][] = $id;
+    }
+    return $found;
+  }
+
+  /**
+   * Find elements within a page, method and selector
+   * @param $pageId
+   * @param $elementId
+   * @param $method
+   * @param $selector
+   * @return mixed
+   */
+  public function findWithin($pageId, $elementId, $method, $selector) {
+    return $this->command('find_within', $pageId, $elementId, $method, $selector);
+  }
+
+  /**
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function getParents($pageId, $elementId) {
+    return $this->command('parents', $pageId, $elementId);
+  }
+
+  /**
+   * Returns the text of a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function allText($pageId, $elementId) {
+    return $this->command('all_text', $pageId, $elementId);
+  }
+
+  /**
+   * Returns the inner or outer html of the given page and element
+   * @param $pageId
+   * @param $elementId
+   * @param $type
+   * @return mixed
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function allHtml($pageId, $elementId, $type = "inner") {
+    return $this->command('all_html', $pageId, $elementId, $type);
+  }
+
+  /**
+   * Returns ONLY the visible text of a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function visibleText($pageId, $elementId) {
+    return $this->command('visible_text', $pageId, $elementId);
+  }
+
+  /**
+   * Deletes the text of a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function deleteText($pageId, $elementId) {
+    return $this->command('delete_text', $pageId, $elementId);
+  }
+
+  /**
+   * Gets the tag name of a given element and page
+   * @param $pageId
+   * @param $elementId
+   * @return string
+   */
+  public function tagName($pageId, $elementId) {
+    return strtolower($this->command('tag_name', $pageId, $elementId));
+  }
+
+  /**
+   * Check if two elements are the same on a give
+   * @param $pageId
+   * @param $firstId
+   * @param $secondId
+   * @return bool
+   */
+  public function equals($pageId, $firstId, $secondId) {
+    return $this->command('equals', $pageId, $firstId, $secondId);
+  }
+
+  /**
+   * Returns the attributes of an element in a given page
+   * @param $pageId
+   * @param $elementId
+   * @return mixed
+   */
+  public function attributes($pageId, $elementId) {
+    return $this->command('attributes', $pageId, $elementId);
+  }
+
+  /**
+   * Returns the attribute of an element by name in a given page
+   * @param $pageId
+   * @param $elementId
+   * @param $name
+   * @return mixed
+   */
+  public function attribute($pageId, $elementId, $name) {
+    return $this->command('attribute', $pageId, $elementId, $name);
+  }
+
+  /**
+   * Set an attribute to the given element in the given page
+   * @param $pageId
+   * @param $elementId
+   * @param $name
+   * @param $value
+   * @return mixed
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function setAttribute($pageId, $elementId, $name, $value) {
+    return $this->command('set_attribute', $pageId, $elementId, $name, $value);
+  }
+
+  /**
+   * Remove an attribute for a given page and element
+   * @param $pageId
+   * @param $elementId
+   * @param $name
+   * @return mixed
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function removeAttribute($pageId, $elementId, $name) {
+    return $this->command('remove_attribute', $pageId, $elementId, $name);
+  }
+
+  /**
+   * Checks if an element is visible or not
+   * @param $pageId
+   * @param $elementId
+   * @return boolean
+   */
+  public function isVisible($pageId, $elementId) {
+    return $this->command("visible", $pageId, $elementId);
+  }
+
+  /**
+   * Sends the order to execute a key event on a given element
+   * @param $pageId
+   * @param $elementId
+   * @param $keyEvent
+   * @param $key
+   * @param $modifier
+   * @return mixed
+   */
+  public function keyEvent($pageId, $elementId, $keyEvent, $key, $modifier) {
+    return $this->command("key_event", $pageId, $elementId, $keyEvent, $key, $modifier);
+  }
+
+  /**
+   * Sends the command to select and option given a value
+   * @param      $pageId
+   * @param      $elementId
+   * @param      $value
+   * @param bool $multiple
+   * @return mixed
+   */
+  public function selectOption($pageId, $elementId, $value, $multiple = false) {
+    return $this->command("select_option", $pageId, $elementId, $value, $multiple);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php
new file mode 100644
index 0000000..3d5f9f1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserPageTrait.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserPageTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserPageTrait {
+  /**
+   * Gets the status code of the request we are currently in
+   * @return mixed
+   */
+  public function getStatusCode() {
+    return $this->command('status_code');
+  }
+
+  /**
+   * Returns the body of the response to a given browser request
+   * @return mixed
+   */
+  public function getBody() {
+    return $this->command('body');
+  }
+
+  /**
+   * Returns the source of the current page
+   * @return mixed
+   */
+  public function getSource() {
+    return $this->command('source');
+  }
+
+  /**
+   * Gets the current page title
+   * @return mixed
+   */
+  public function getTitle() {
+    return $this->command('title');
+  }
+
+  /**
+   * Resize the current page
+   * @param $width
+   * @param $height
+   * @return mixed
+   */
+  public function resize($width, $height) {
+    return $this->command('resize', $width, $height);
+  }
+
+  /**
+   * Resets the page we are in to a clean slate
+   * @return mixed
+   */
+  public function reset() {
+    return $this->command('reset');
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php
new file mode 100644
index 0000000..3aa10aa
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserRenderTrait.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserRenderTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserRenderTrait {
+  /**
+   * Check and fix render options
+   * @param $options
+   * @return mixed
+   */
+  protected function checkRenderOptions($options) {
+    //Default is full and no selection
+    if (count($options) === 0) {
+      $options["full"] = true;
+      $options["selector"] = null;
+    }
+
+    if (isset($options["full"]) && isset($options["selector"])) {
+      if ($options["full"]) {
+        //Whatever it is, full is more powerful than selection
+        $options["selector"] = null;
+      }
+    } else {
+      if (!isset($options["full"]) && isset($options["selector"])) {
+        $options["full"] = false;
+      }
+    }
+    return $options;
+  }
+
+  /**
+   * Renders a page or selection to a file given by path
+   * @param string $path
+   * @param array  $options
+   * @return mixed
+   */
+  public function render($path, $options = array()) {
+    $fixedOptions = $this->checkRenderOptions($options);
+    return $this->command('render', $path, $fixedOptions["full"], $fixedOptions["selector"]);
+  }
+
+  /**
+   * Renders base64 a page or selection to a file given by path
+   * @param string $imageFormat (PNG, GIF, JPEG)
+   * @param array  $options
+   * @return mixed
+   */
+  public function renderBase64($imageFormat, $options = array()) {
+    $fixedOptions = $this->checkRenderOptions($options);
+    return $this->command('render_base64', $imageFormat, $fixedOptions["full"], $fixedOptions["selector"]);
+  }
+
+  /**
+   * Sets the paper size, useful when saving to PDF
+   * @param $paperSize
+   * @return mixed
+   */
+  public function setPaperSize($paperSize) {
+    return $this->command('set_paper_size', $paperSize);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php
new file mode 100644
index 0000000..769b86f
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserScriptTrait.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Trait BrowserScriptTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserScriptTrait {
+  /**
+   * Evaluates a script on the browser
+   * @param $script
+   * @return mixed
+   */
+  public function evaluate($script) {
+    return $this->command('evaluate', $script);
+  }
+
+  /**
+   * Executes a script on the browser
+   * @param $script
+   * @return mixed
+   */
+  public function execute($script) {
+    return $this->command('execute', $script);
+  }
+
+  /**
+   * Add desired extensions to phantomjs
+   * @param $extensions
+   * @return bool
+   */
+  public function extensions($extensions) {
+    //TODO: add error control for when extensions do not exist physically
+    foreach ($extensions as $extensionName) {
+      $this->command('add_extension', $extensionName);
+    }
+    return true;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php
new file mode 100644
index 0000000..8647ffc
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Browser/BrowserWindowTrait.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Zumba\GastonJS\Browser;
+
+/**
+ * Class BrowserWindowTrait
+ * @package Zumba\GastonJS\Browser
+ */
+trait BrowserWindowTrait {
+  /**
+   * Returns the current window handle name in the browser
+   * @param string $name
+   * @return mixed
+   */
+  public function windowHandle($name = null) {
+    return $this->command('window_handle', $name);
+  }
+
+  /**
+   * Returns all the window handles present in the browser
+   * @return array
+   */
+  public function windowHandles() {
+    return $this->command('window_handles');
+  }
+
+  /**
+   * Change the browser focus to another window
+   * @param $windowHandleName
+   * @return mixed
+   */
+  public function switchToWindow($windowHandleName) {
+    return $this->command('switch_to_window', $windowHandleName);
+  }
+
+  /**
+   * Opens a new window on the browser
+   * @return mixed
+   */
+  public function openNewWindow() {
+    return $this->command('open_new_window');
+  }
+
+  /**
+   * Closes a window on the browser by a given handler name
+   * @param $windowHandleName
+   * @return mixed
+   */
+  public function closeWindow($windowHandleName) {
+    return $this->command('close_window', $windowHandleName);
+  }
+
+  /**
+   * Gets the current request window name
+   * @return string
+   * @throws \Zumba\GastonJS\Exception\BrowserError
+   * @throws \Exception
+   */
+  public function windowName() {
+    return $this->command('window_name');
+  }
+
+  /**
+   * Zoom factor for a web page
+   * @param $zoomFactor
+   * @return mixed
+   */
+  public function setZoomFactor($zoomFactor) {
+    return $this->command('set_zoom_factor', $zoomFactor);
+  }
+
+  /**
+   * Gets the window size
+   * @param $windowHandleName
+   * @return mixed
+   */
+  public function windowSize($windowHandleName) {
+    return $this->command('window_size', $windowHandleName);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js
new file mode 100644
index 0000000..892333c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/browser_error.js
@@ -0,0 +1,17 @@
+Poltergeist.BrowserError = (function (_super) {
+  __extends(BrowserError, _super);
+
+  function BrowserError(message, stack) {
+    this.message = message;
+    this.stack = stack;
+  }
+
+  BrowserError.prototype.name = "Poltergeist.BrowserError";
+
+  BrowserError.prototype.args = function () {
+    return [this.message, this.stack];
+  };
+
+  return BrowserError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js
new file mode 100644
index 0000000..5a6f1f6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/error.js
@@ -0,0 +1,10 @@
+/**
+ *  Poltergeist base error class
+ */
+Poltergeist.Error = (function () {
+  function Error() {
+  }
+
+  return Error;
+
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js
new file mode 100644
index 0000000..d42e872
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/frame_not_found.js
@@ -0,0 +1,16 @@
+Poltergeist.FrameNotFound = (function (_super) {
+  __extends(FrameNotFound, _super);
+
+  function FrameNotFound(frameName) {
+    this.frameName = frameName;
+  }
+
+  FrameNotFound.prototype.name = "Poltergeist.FrameNotFound";
+
+  FrameNotFound.prototype.args = function () {
+    return [this.frameName];
+  };
+
+  return FrameNotFound;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js
new file mode 100644
index 0000000..2ef4ae9
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/invalid_selector.js
@@ -0,0 +1,17 @@
+Poltergeist.InvalidSelector = (function (_super) {
+  __extends(InvalidSelector, _super);
+
+  function InvalidSelector(method, selector) {
+    this.method = method;
+    this.selector = selector;
+  }
+
+  InvalidSelector.prototype.name = "Poltergeist.InvalidSelector";
+
+  InvalidSelector.prototype.args = function () {
+    return [this.method, this.selector];
+  };
+
+  return InvalidSelector;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js
new file mode 100644
index 0000000..b8679e4
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/javascript_error.js
@@ -0,0 +1,16 @@
+Poltergeist.JavascriptError = (function (_super) {
+  __extends(JavascriptError, _super);
+
+  function JavascriptError(errors) {
+    this.errors = errors;
+  }
+
+  JavascriptError.prototype.name = "Poltergeist.JavascriptError";
+
+  JavascriptError.prototype.args = function () {
+    return [this.errors];
+  };
+
+  return JavascriptError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js
new file mode 100644
index 0000000..f3d4e85
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/mouse_event_failed.js
@@ -0,0 +1,18 @@
+Poltergeist.MouseEventFailed = (function (_super) {
+  __extends(MouseEventFailed, _super);
+
+  function MouseEventFailed(eventName, selector, position) {
+    this.eventName = eventName;
+    this.selector = selector;
+    this.position = position;
+  }
+
+  MouseEventFailed.prototype.name = "Poltergeist.MouseEventFailed";
+
+  MouseEventFailed.prototype.args = function () {
+    return [this.eventName, this.selector, this.position];
+  };
+
+  return MouseEventFailed;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js
new file mode 100644
index 0000000..ee1d5ad
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/no_such_window_error.js
@@ -0,0 +1,17 @@
+Poltergeist.NoSuchWindowError = (function (_super) {
+  __extends(NoSuchWindowError, _super);
+
+  function NoSuchWindowError() {
+    _ref2 = NoSuchWindowError.__super__.constructor.apply(this, arguments);
+    return _ref2;
+  }
+
+  NoSuchWindowError.prototype.name = "Poltergeist.NoSuchWindowError";
+
+  NoSuchWindowError.prototype.args = function () {
+    return [];
+  };
+
+  return NoSuchWindowError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js
new file mode 100644
index 0000000..758cfd6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/obsolete_node.js
@@ -0,0 +1,21 @@
+Poltergeist.ObsoleteNode = (function (_super) {
+  __extends(ObsoleteNode, _super);
+
+  function ObsoleteNode() {
+    _ref = ObsoleteNode.__super__.constructor.apply(this, arguments);
+    return _ref;
+  }
+
+  ObsoleteNode.prototype.name = "Poltergeist.ObsoleteNode";
+
+  ObsoleteNode.prototype.args = function () {
+    return [];
+  };
+
+  ObsoleteNode.prototype.toString = function () {
+    return this.name;
+  };
+
+  return ObsoleteNode;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js
new file mode 100644
index 0000000..55f1871
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Errors/status_fail_error.js
@@ -0,0 +1,17 @@
+Poltergeist.StatusFailError = (function (_super) {
+  __extends(StatusFailError, _super);
+
+  function StatusFailError() {
+    _ref1 = StatusFailError.__super__.constructor.apply(this, arguments);
+    return _ref1;
+  }
+
+  StatusFailError.prototype.name = "Poltergeist.StatusFailError";
+
+  StatusFailError.prototype.args = function () {
+    return [];
+  };
+
+  return StatusFailError;
+
+})(Poltergeist.Error);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js b/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js
new file mode 100644
index 0000000..120d1fd
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Server/server.js
@@ -0,0 +1,80 @@
+Poltergeist.Server = (function () {
+
+  /**
+   * Server constructor
+   * @param owner
+   * @param port
+   * @constructor
+   */
+  function Server(owner, port) {
+    this.server = require('webserver').create();
+    this.port = port;
+    this.owner = owner;
+    this.webServer = null;
+  }
+
+  /**
+   * Starts the web server
+   */
+  Server.prototype.start = function () {
+    var self = this;
+    this.webServer = this.server.listen(this.port, function (request, response) {
+      self.handleRequest(request, response);
+    });
+  };
+
+  /**
+   * Send error back with code and message
+   * @param response
+   * @param code
+   * @param message
+   * @return {boolean}
+   */
+  Server.prototype.sendError = function (response, code, message) {
+    response.statusCode = code;
+    response.setHeader('Content-Type', 'application/json');
+    response.write(JSON.stringify(message, null, 4));
+    response.close();
+    return true;
+  };
+
+
+  /**
+   * Send response back to the client
+   * @param response
+   * @param data
+   * @return {boolean}
+   */
+  Server.prototype.send = function (response, data) {
+    console.log("RESPONSE: " + JSON.stringify(data, null, 4).substr(0, 200));
+
+    response.statusCode = 200;
+    response.setHeader('Content-Type', 'application/json');
+    response.write(JSON.stringify(data, null, 4));
+    response.close();
+    return true;
+  };
+
+  /**
+   * Handles a request to the server
+   * @param request
+   * @param response
+   * @return {boolean}
+   */
+  Server.prototype.handleRequest = function (request, response) {
+    var commandData;
+    if (request.method !== "POST") {
+      return this.sendError(response, 405, "Only POST method is allowed in the service");
+    }
+    console.log("REQUEST: " + request.post + "\n");
+    try {
+      commandData = JSON.parse(request.post);
+    } catch (parseError) {
+      return this.sendError(response, 400, "JSON data invalid error: " + parseError.message);
+    }
+
+    return this.owner.serverRunCommand(commandData, response);
+  };
+
+  return Server;
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js b/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js
new file mode 100644
index 0000000..a67a75c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/Tools/inherit.js
@@ -0,0 +1,28 @@
+var __extends;
+/**
+ * Helper function so objects can inherit from another
+ * @param child
+ * @param parent
+ * @return {Object}
+ * @private
+ */
+__extends = function (child, parent) {
+  var __hasProp;
+  __hasProp = {}.hasOwnProperty;
+  for (var key in parent) {
+    if (parent.hasOwnProperty(key)) {
+      if (__hasProp.call(parent, key)) {
+        child[key] = parent[key];
+      }
+    }
+  }
+
+  function ClassConstructor() {
+    this.constructor = child;
+  }
+
+  ClassConstructor.prototype = parent.prototype;
+  child.prototype = new ClassConstructor();
+  child.__super__ = parent.prototype;
+  return child;
+};
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/agent.js b/vendor/jcalderonzumba/gastonjs/src/Client/agent.js
new file mode 100644
index 0000000..606a6c1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/agent.js
@@ -0,0 +1,896 @@
+var PoltergeistAgent;
+
+PoltergeistAgent = (function () {
+  function PoltergeistAgent() {
+    this.elements = [];
+    this.nodes = {};
+  }
+
+  /**
+   * Executes an external call done from the web page class
+   * @param name
+   * @param args
+   * @return {*}
+   */
+  PoltergeistAgent.prototype.externalCall = function (name, args) {
+    var error;
+    try {
+      return {
+        value: this[name].apply(this, args)
+      };
+    } catch (_error) {
+      error = _error;
+      return {
+        error: {
+          message: error.toString(),
+          stack: error.stack
+        }
+      };
+    }
+  };
+
+  /**
+   * Object stringifycation
+   * @param object
+   * @return {*}
+   */
+  PoltergeistAgent.stringify = function (object) {
+    var error;
+    try {
+      return JSON.stringify(object, function (key, value) {
+        if (Array.isArray(this[key])) {
+          return this[key];
+        } else {
+          return value;
+        }
+      });
+    } catch (_error) {
+      error = _error;
+      if (error instanceof TypeError) {
+        return '"(cyclic structure)"';
+      } else {
+        throw error;
+      }
+    }
+  };
+
+  /**
+   * Name speaks for itself
+   * @return {string}
+   */
+  PoltergeistAgent.prototype.currentUrl = function () {
+    return encodeURI(decodeURI(window.location.href));
+  };
+
+  /**
+   *  Given a method of selection (xpath or css), a selector and a possible element to search
+   *  tries to find the elements that matches such selection
+   * @param method
+   * @param selector
+   * @param within
+   * @return {Array}
+   */
+  PoltergeistAgent.prototype.find = function (method, selector, within) {
+    var elementForXpath, error, i, results, xpath, _i, _len, _results;
+    if (within == null) {
+      within = document;
+    }
+    try {
+      if (method === "xpath") {
+        xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+        results = (function () {
+          var _i, _ref, _results;
+          _results = [];
+          for (i = _i = 0, _ref = xpath.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
+            _results.push(xpath.snapshotItem(i));
+          }
+          return _results;
+        })();
+      } else {
+        results = within.querySelectorAll(selector);
+      }
+      _results = [];
+      for (_i = 0, _len = results.length; _i < _len; _i++) {
+        elementForXpath = results[_i];
+        _results.push(this.register(elementForXpath));
+      }
+      return _results;
+    } catch (_error) {
+      error = _error;
+      if (error.code === DOMException.SYNTAX_ERR || error.code === 51) {
+        throw new PoltergeistAgent.InvalidSelector;
+      } else {
+        throw error;
+      }
+    }
+  };
+
+  /**
+   *  Register the element in the agent
+   * @param element
+   * @return {number}
+   */
+  PoltergeistAgent.prototype.register = function (element) {
+    this.elements.push(element);
+    return this.elements.length - 1;
+  };
+
+  /**
+   *  Gets the size of the document
+   * @return {{height: number, width: number}}
+   */
+  PoltergeistAgent.prototype.documentSize = function () {
+    return {
+      height: document.documentElement.scrollHeight || document.documentElement.clientHeight,
+      width: document.documentElement.scrollWidth || document.documentElement.clientWidth
+    };
+  };
+
+  /**
+   * Gets a Node by a given id
+   * @param id
+   * @return {PoltergeistAgent.Node}
+   */
+  PoltergeistAgent.prototype.get = function (id) {
+    if (typeof this.nodes[id] == "undefined" || this.nodes[id] === null) {
+      //Let's try now the elements approach
+      if (typeof this.elements[id] == "undefined" || this.elements[id] === null) {
+        throw new PoltergeistAgent.ObsoleteNode;
+      }
+      return new PoltergeistAgent.Node(this, this.elements[id]);
+    }
+
+    return this.nodes[id];
+  };
+
+  /**
+   * Calls a Node agent function from the Node caller via delegates
+   * @param id
+   * @param name
+   * @param args
+   * @return {*}
+   */
+  PoltergeistAgent.prototype.nodeCall = function (id, name, args) {
+    var node;
+
+    node = this.get(id);
+    if (node.isObsolete()) {
+      throw new PoltergeistAgent.ObsoleteNode;
+    }
+    //TODO: add some error control here, we might not be able to call name function
+    return node[name].apply(node, args);
+  };
+
+  PoltergeistAgent.prototype.beforeUpload = function (id) {
+    return this.get(id).setAttribute('_poltergeist_selected', '');
+  };
+
+  PoltergeistAgent.prototype.afterUpload = function (id) {
+    return this.get(id).removeAttribute('_poltergeist_selected');
+  };
+
+  PoltergeistAgent.prototype.clearLocalStorage = function () {
+    //TODO: WTF where is variable...
+    return localStorage.clear();
+  };
+
+  return PoltergeistAgent;
+
+})();
+
+PoltergeistAgent.ObsoleteNode = (function () {
+  function ObsoleteNode() {
+  }
+
+  ObsoleteNode.prototype.toString = function () {
+    return "PoltergeistAgent.ObsoleteNode";
+  };
+
+  return ObsoleteNode;
+
+})();
+
+PoltergeistAgent.InvalidSelector = (function () {
+  function InvalidSelector() {
+  }
+
+  InvalidSelector.prototype.toString = function () {
+    return "PoltergeistAgent.InvalidSelector";
+  };
+
+  return InvalidSelector;
+
+})();
+
+PoltergeistAgent.Node = (function () {
+
+  Node.EVENTS = {
+    FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
+    MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'contextmenu'],
+    FORM: ['submit']
+  };
+
+  function Node(agent, element) {
+    this.agent = agent;
+    this.element = element;
+  }
+
+  /**
+   * Give me the node id of the parent of this node
+   * @return {number}
+   */
+  Node.prototype.parentId = function () {
+    return this.agent.register(this.element.parentNode);
+  };
+
+  /**
+   * Returns all the node parents ids up to first child of the dom
+   * @return {Array}
+   */
+  Node.prototype.parentIds = function () {
+    var ids, parent;
+    ids = [];
+    parent = this.element.parentNode;
+    while (parent !== document) {
+      ids.push(this.agent.register(parent));
+      parent = parent.parentNode;
+    }
+    return ids;
+  };
+
+  /**
+   * Finds and returns the node ids that matches the selector within this node
+   * @param method
+   * @param selector
+   * @return {Array}
+   */
+  Node.prototype.find = function (method, selector) {
+    return this.agent.find(method, selector, this.element);
+  };
+
+  /**
+   * Checks whether the node is obsolete or not
+   * @return boolean
+   */
+  Node.prototype.isObsolete = function () {
+    var obsolete;
+
+    obsolete = function (element) {
+      if (element.parentNode != null) {
+        if (element.parentNode === document) {
+          return false;
+        } else {
+          return obsolete(element.parentNode);
+        }
+      } else {
+        return true;
+      }
+    };
+
+    return obsolete(this.element);
+  };
+
+  Node.prototype.changed = function () {
+    var event;
+    event = document.createEvent('HTMLEvents');
+    event.initEvent('change', true, false);
+    return this.element.dispatchEvent(event);
+  };
+
+  Node.prototype.input = function () {
+    var event;
+    event = document.createEvent('HTMLEvents');
+    event.initEvent('input', true, false);
+    return this.element.dispatchEvent(event);
+  };
+
+  Node.prototype.keyupdowned = function (eventName, keyCode) {
+    var event;
+    event = document.createEvent('UIEvents');
+    event.initEvent(eventName, true, true);
+    event.keyCode = keyCode;
+    event.which = keyCode;
+    event.charCode = 0;
+    return this.element.dispatchEvent(event);
+  };
+
+  Node.prototype.keypressed = function (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
+    var event;
+    event = document.createEvent('UIEvents');
+    event.initEvent('keypress', true, true);
+    event.window = this.agent.window;
+    event.altKey = altKey;
+    event.ctrlKey = ctrlKey;
+    event.shiftKey = shiftKey;
+    event.metaKey = metaKey;
+    event.keyCode = keyCode;
+    event.charCode = charCode;
+    event.which = keyCode;
+    return this.element.dispatchEvent(event);
+  };
+
+  /**
+   * Tells if the node is inside the body of the document and not somewhere else
+   * @return {boolean}
+   */
+  Node.prototype.insideBody = function () {
+    return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
+  };
+
+  /**
+   * Returns all text visible or not of the node
+   * @return {string}
+   */
+  Node.prototype.allText = function () {
+    return this.element.textContent;
+  };
+
+  /**
+   * Returns the inner html our outer
+   * @returns {string}
+   */
+  Node.prototype.allHTML = function (type) {
+    var returnType = type || 'inner';
+
+    if (returnType === "inner") {
+      return this.element.innerHTML;
+    }
+
+    if (returnType === "outer") {
+      if (this.element.outerHTML) {
+        return this.element.outerHTML;
+      }
+      // polyfill:
+      var wrapper = document.createElement('div');
+      wrapper.appendChild(this.element.cloneNode(true));
+      return wrapper.innerHTML;
+    }
+
+    return '';
+  };
+
+  /**
+   * If the element is visible then we return the text
+   * @return {string}
+   */
+  Node.prototype.visibleText = function () {
+    if (!this.isVisible(null)) {
+      return null;
+    }
+
+    if (this.element.nodeName === "TEXTAREA") {
+      return this.element.textContent;
+    }
+
+    return this.element.innerText;
+  };
+
+  /**
+   * Deletes the actual text being represented by a selection object from the node's element DOM.
+   * @return {*}
+   */
+  Node.prototype.deleteText = function () {
+    var range;
+    range = document.createRange();
+    range.selectNodeContents(this.element);
+    window.getSelection().removeAllRanges();
+    window.getSelection().addRange(range);
+    return window.getSelection().deleteFromDocument();
+  };
+
+  /**
+   * Returns all the attributes {name:value} in the element
+   * @return {{}}
+   */
+  Node.prototype.getAttributes = function () {
+    var attributes, i, elementAttributes;
+
+    elementAttributes = this.element.attributes;
+    attributes = {};
+    for (i = 0; i < elementAttributes.length; i++) {
+      attributes[elementAttributes[i].name] = elementAttributes[i].value.replace("\n", "\\n");
+    }
+
+    return attributes;
+  };
+
+  /**
+   * Name speaks for it self, returns the value of a given attribute by name
+   * @param name
+   * @return {string}
+   */
+  Node.prototype.getAttribute = function (name) {
+    if (name === 'checked' || name === 'selected' || name === 'multiple') {
+      return this.element[name];
+    }
+    return this.element.getAttribute(name);
+  };
+
+  /**
+   * Scrolls the current element into the visible area of the browser window
+   * @return {*}
+   */
+  Node.prototype.scrollIntoView = function () {
+    return this.element.scrollIntoViewIfNeeded();
+  };
+
+  /**
+   *  Returns the element.value property with special treatment if the element is a select
+   * @return {*}
+   */
+  Node.prototype.value = function () {
+    var options, i, values;
+
+    if (this.element.tagName.toLowerCase() === 'select' && this.element.multiple) {
+      values = [];
+      options = this.element.children;
+      for (i = 0; i < options.length; i++) {
+        if (options[i].selected) {
+          values.push(options[i].value);
+        }
+      }
+      return values;
+    }
+
+    return this.element.value;
+  };
+
+  /**
+   * Sets a given value in the element value property by simulation key interaction
+   * @param value
+   * @return {*}
+   */
+  Node.prototype.set = function (value) {
+    var char, keyCode, i, len;
+
+    if (this.element.readOnly) {
+      return null;
+    }
+
+    //respect the maxLength property if present
+    if (this.element.maxLength >= 0) {
+      value = value.substr(0, this.element.maxLength);
+    }
+
+    this.element.value = '';
+    this.trigger('focus');
+
+    if (this.element.type === 'number') {
+      this.element.value = value;
+    } else {
+      for (i = 0, len = value.length; i < len; i++) {
+        char = value[i];
+        keyCode = this.characterToKeyCode(char);
+        this.keyupdowned('keydown', keyCode);
+        this.element.value += char;
+        this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
+        this.keyupdowned('keyup', keyCode);
+      }
+    }
+
+    this.changed();
+    this.input();
+
+    return this.trigger('blur');
+  };
+
+  /**
+   * Is the node multiple
+   * @return {boolean}
+   */
+  Node.prototype.isMultiple = function () {
+    return this.element.multiple;
+  };
+
+  /**
+   * Sets the value of an attribute given by name
+   * @param name
+   * @param value
+   * @return {boolean}
+   */
+  Node.prototype.setAttribute = function (name, value) {
+    if (value === null) {
+      return this.removeAttribute(name);
+    }
+
+    this.element.setAttribute(name, value);
+    return true;
+  };
+
+  /**
+   *  Removes and attribute by name
+   * @param name
+   * @return {boolean}
+   */
+  Node.prototype.removeAttribute = function (name) {
+    this.element.removeAttribute(name);
+    return true;
+  };
+
+  /**
+   *  Selects the current node
+   * @param value
+   * @return {boolean}
+   */
+  Node.prototype.select = function (value) {
+    if (value === false && !this.element.parentNode.multiple) {
+      return false;
+    }
+
+    this.element.selected = value;
+    this.changed();
+    return true;
+  };
+
+  /**
+   * Selects the radio button that has the defined value
+   * @param value
+   * @return {boolean}
+   */
+  Node.prototype.selectRadioValue = function (value) {
+    if (this.element.value == value) {
+      this.element.checked = true;
+      this.trigger('focus');
+      this.trigger('click');
+      this.changed();
+      return true;
+    }
+
+    var formElements = this.element.form.elements;
+    var name = this.element.getAttribute('name');
+    var element, i;
+
+    var deselectAllRadios = function (elements, radioName) {
+      var inputRadioElement;
+
+      for (i = 0; i < elements.length; i++) {
+        inputRadioElement = elements[i];
+        if (inputRadioElement.tagName.toLowerCase() == 'input' && inputRadioElement.type.toLowerCase() == 'radio' && inputRadioElement.name == radioName) {
+          inputRadioElement.checked = false;
+        }
+      }
+    };
+
+    var radioChange = function (radioElement) {
+      var radioEvent;
+      radioEvent = document.createEvent('HTMLEvents');
+      radioEvent.initEvent('change', true, false);
+      return radioElement.dispatchEvent(radioEvent);
+    };
+
+    var radioClickEvent = function (radioElement, name) {
+      var radioEvent;
+      radioEvent = document.createEvent('MouseEvent');
+      radioEvent.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+      return radioElement.dispatchEvent(radioEvent);
+    };
+
+    if (!name) {
+      throw new Poltergeist.BrowserError('The radio button does not have the value "' + value + '"');
+    }
+
+    for (i = 0; i < formElements.length; i++) {
+      element = formElements[i];
+      if (element.tagName.toLowerCase() == 'input' && element.type.toLowerCase() == 'radio' && element.name === name) {
+        if (value === element.value) {
+          deselectAllRadios(formElements, name);
+          element.checked = true;
+          radioClickEvent(element, 'click');
+          radioChange(element);
+          return true;
+        }
+      }
+    }
+
+    throw new Poltergeist.BrowserError('The radio group "' + name + '" does not have an option "' + value + '"');
+  };
+
+  /**
+   *  Checks or uncheck a radio option
+   * @param value
+   * @return {boolean}
+   */
+  Node.prototype.checked = function (value) {
+    //TODO: add error control for the checked stuff
+    this.element.checked = value;
+    return true;
+  };
+
+  /**
+   * Returns the element tag name as is, no transformations done
+   * @return {string}
+   */
+  Node.prototype.tagName = function () {
+    return this.element.tagName;
+  };
+
+  /**
+   * Checks if the element is visible either by itself of because the parents are visible
+   * @param element
+   * @return {boolean}
+   */
+  Node.prototype.isVisible = function (element) {
+    var nodeElement = element || this.element;
+
+    if (window.getComputedStyle(nodeElement).display === 'none') {
+      return false;
+    } else if (nodeElement.parentElement) {
+      return this.isVisible(nodeElement.parentElement);
+    } else {
+      return true;
+    }
+  };
+
+  /**
+   * Is the node disabled for operations with it?
+   * @return {boolean}
+   */
+  Node.prototype.isDisabled = function () {
+    return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled;
+  };
+
+  /**
+   * Does the node contains the selections
+   * @return {boolean}
+   */
+  Node.prototype.containsSelection = function () {
+    var selectedNode;
+
+    selectedNode = document.getSelection().focusNode;
+    if (!selectedNode) {
+      return false;
+    }
+    //this magic number is NODE.TEXT_NODE
+    if (selectedNode.nodeType === 3) {
+      selectedNode = selectedNode.parentNode;
+    }
+
+    return this.element.contains(selectedNode);
+  };
+
+  /**
+   * Returns the offset of the node in relation to the current frame
+   * @return {{top: number, left: number}}
+   */
+  Node.prototype.frameOffset = function () {
+    var offset, rect, style, win;
+    win = window;
+    offset = {
+      top: 0,
+      left: 0
+    };
+    while (win.frameElement) {
+      rect = win.frameElement.getClientRects()[0];
+      style = win.getComputedStyle(win.frameElement);
+      win = win.parent;
+      offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10);
+      offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10);
+    }
+    return offset;
+  };
+
+  /**
+   * Returns the object position in relation to the window
+   * @return {{top: *, right: *, left: *, bottom: *, width: *, height: *}}
+   */
+  Node.prototype.position = function () {
+    var frameOffset, pos, rect;
+
+    rect = this.element.getClientRects()[0];
+    if (!rect) {
+      throw new PoltergeistAgent.ObsoleteNode;
+    }
+
+    frameOffset = this.frameOffset();
+    pos = {
+      top: rect.top + frameOffset.top,
+      right: rect.right + frameOffset.left,
+      left: rect.left + frameOffset.left,
+      bottom: rect.bottom + frameOffset.top,
+      width: rect.width,
+      height: rect.height
+    };
+
+    return pos;
+  };
+
+  /**
+   * Triggers a DOM event related to the node element
+   * @param name
+   * @return {boolean}
+   */
+  Node.prototype.trigger = function (name) {
+    var event;
+    if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
+      event = document.createEvent('MouseEvent');
+      event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+    } else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
+      event = this.obtainEvent(name);
+    } else if (Node.EVENTS.FORM.indexOf(name) !== -1) {
+      event = this.obtainEvent(name);
+    } else {
+      throw "Unknown event";
+    }
+    return this.element.dispatchEvent(event);
+  };
+
+  /**
+   * Creates a generic HTMLEvent to be use in the node element
+   * @param name
+   * @return {Event}
+   */
+  Node.prototype.obtainEvent = function (name) {
+    var event;
+    event = document.createEvent('HTMLEvents');
+    event.initEvent(name, true, true);
+    return event;
+  };
+
+  /**
+   * Does a check to see if the coordinates given
+   * match the node element or some of the parents chain
+   * @param x
+   * @param y
+   * @return {*}
+   */
+  Node.prototype.mouseEventTest = function (x, y) {
+    var elementForXpath, frameOffset, origEl;
+
+    frameOffset = this.frameOffset();
+    x -= frameOffset.left;
+    y -= frameOffset.top;
+
+    elementForXpath = origEl = document.elementFromPoint(x, y);
+    while (elementForXpath) {
+      if (elementForXpath === this.element) {
+        return {
+          status: 'success'
+        };
+      } else {
+        elementForXpath = elementForXpath.parentNode;
+      }
+    }
+
+    return {
+      status: 'failure',
+      selector: origEl && this.getSelector(origEl)
+    };
+  };
+
+  /**
+   * Returns the node selector in CSS style (NO xpath)
+   * @param elementForXpath
+   * @return {string}
+   */
+  Node.prototype.getSelector = function (elementForXpath) {
+    var className, selector, i, len, classNames;
+
+    selector = elementForXpath.tagName !== 'HTML' ? this.getSelector(elementForXpath.parentNode) + ' ' : '';
+    selector += elementForXpath.tagName.toLowerCase();
+
+    if (elementForXpath.id) {
+      selector += "#" + elementForXpath.id;
+    }
+
+    classNames = elementForXpath.classList;
+    for (i = 0, len = classNames.length; i < len; i++) {
+      className = classNames[i];
+      selector += "." + className;
+    }
+
+    return selector;
+  };
+
+  /**
+   * Returns the key code that represents the character
+   * @param character
+   * @return {number}
+   */
+  Node.prototype.characterToKeyCode = function (character) {
+    var code, specialKeys;
+    code = character.toUpperCase().charCodeAt(0);
+    specialKeys = {
+      96: 192,
+      45: 189,
+      61: 187,
+      91: 219,
+      93: 221,
+      92: 220,
+      59: 186,
+      39: 222,
+      44: 188,
+      46: 190,
+      47: 191,
+      127: 46,
+      126: 192,
+      33: 49,
+      64: 50,
+      35: 51,
+      36: 52,
+      37: 53,
+      94: 54,
+      38: 55,
+      42: 56,
+      40: 57,
+      41: 48,
+      95: 189,
+      43: 187,
+      123: 219,
+      125: 221,
+      124: 220,
+      58: 186,
+      34: 222,
+      60: 188,
+      62: 190,
+      63: 191
+    };
+    return specialKeys[code] || code;
+  };
+
+  /**
+   * Checks if one element is equal to other given by its node id
+   * @param other_id
+   * @return {boolean}
+   */
+  Node.prototype.isDOMEqual = function (other_id) {
+    return this.element === this.agent.get(other_id).element;
+  };
+
+  /**
+   * The following function allows one to pass an element and an XML document to find a unique string XPath expression leading back to that element.
+   * @param element
+   * @return {string}
+   */
+  Node.prototype.getXPathForElement = function (element) {
+    var elementForXpath = element || this.element;
+    var xpath = '';
+    var pos, tempitem2;
+
+    while (elementForXpath !== document.documentElement) {
+      pos = 0;
+      tempitem2 = elementForXpath;
+      while (tempitem2) {
+        if (tempitem2.nodeType === 1 && tempitem2.nodeName === elementForXpath.nodeName) { // If it is ELEMENT_NODE of the same name
+          pos += 1;
+        }
+        tempitem2 = tempitem2.previousSibling;
+      }
+
+      xpath = "*[name()='" + elementForXpath.nodeName + "' and namespace-uri()='" + (elementForXpath.namespaceURI === null ? '' : elementForXpath.namespaceURI) + "'][" + pos + ']' + '/' + xpath;
+
+      elementForXpath = elementForXpath.parentNode;
+    }
+
+    xpath = '/*' + "[name()='" + document.documentElement.nodeName + "' and namespace-uri()='" + (elementForXpath.namespaceURI === null ? '' : elementForXpath.namespaceURI) + "']" + '/' + xpath;
+    xpath = xpath.replace(/\/$/, '');
+    return xpath;
+  };
+
+  /**
+   * Deselect all the options for this element
+   */
+  Node.prototype.deselectAllOptions = function () {
+    //TODO: error control when the node is not a select node
+    var i, l = this.element.options.length;
+    for (i = 0; i < l; i++) {
+      this.element.options[i].selected = false;
+    }
+  };
+
+  return Node;
+
+})();
+
+window.__poltergeist = new PoltergeistAgent;
+
+document.addEventListener('DOMContentLoaded', function () {
+  return console.log('__DOMContentLoaded');
+});
+
+window.confirm = function (message) {
+  return true;
+};
+
+window.prompt = function (message, _default) {
+  return _default || null;
+};
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/browser.js b/vendor/jcalderonzumba/gastonjs/src/Client/browser.js
new file mode 100644
index 0000000..df667fb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/browser.js
@@ -0,0 +1,1293 @@
+var __indexOf = [].indexOf || function (item) {
+    for (var i = 0, l = this.length; i < l; i++) {
+      if (i in this && this[i] === item) return i;
+    }
+    return -1;
+  };
+
+var xpathStringLiteral = function (s) {
+  if (s.indexOf('"') === -1)
+    return '"' + s + '"';
+  if (s.indexOf("'") === -1)
+    return "'" + s + "'";
+  return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
+};
+
+Poltergeist.Browser = (function () {
+  /**
+   * Creates the "browser" inside phantomjs
+   * @param owner
+   * @param width
+   * @param height
+   * @constructor
+   */
+  function Browser(owner, width, height) {
+    this.owner = owner;
+    this.width = width || 1024;
+    this.height = height || 768;
+    this.pages = [];
+    this.js_errors = true;
+    this._debug = false;
+    this._counter = 0;
+    this.resetPage();
+  }
+
+  /**
+   * Resets the browser to a clean slate
+   * @return {Function}
+   */
+  Browser.prototype.resetPage = function () {
+    var _ref;
+    var self = this;
+
+    _ref = [0, []];
+    this._counter = _ref[0];
+    this.pages = _ref[1];
+
+    if (this.page != null) {
+      if (!this.page.closed) {
+        if (this.page.currentUrl() !== 'about:blank') {
+          this.page.clearLocalStorage();
+        }
+        this.page.release();
+      }
+      phantom.clearCookies();
+    }
+
+    this.page = this.currentPage = new Poltergeist.WebPage;
+    this.page.setViewportSize({
+      width: this.width,
+      height: this.height
+    });
+    this.page.handle = "" + (this._counter++);
+    this.pages.push(this.page);
+
+    return this.page.onPageCreated = function (newPage) {
+      var page;
+      page = new Poltergeist.WebPage(newPage);
+      page.handle = "" + (self._counter++);
+      return self.pages.push(page);
+    };
+  };
+
+  /**
+   * Given a page handle id, tries to get it from the browser page list
+   * @param handle
+   * @return {WebPage}
+   */
+  Browser.prototype.getPageByHandle = function (handle) {
+    var filteredPages;
+
+    //TODO: perhaps we should throw a PageNotFoundByHandle or something like that..
+    if (handle === null || typeof handle == "undefined") {
+      return null;
+    }
+
+    filteredPages = this.pages.filter(function (p) {
+      return !p.closed && p.handle === handle;
+    });
+
+    if (filteredPages.length === 1) {
+      return filteredPages[0];
+    }
+
+    return null;
+  };
+
+  /**
+   * Sends a debug message to the console
+   * @param message
+   * @return {*}
+   */
+  Browser.prototype.debug = function (message) {
+    if (this._debug) {
+      return console.log("poltergeist [" + (new Date().getTime()) + "] " + message);
+    }
+  };
+
+  /**
+   * Given a page_id and id, gets if possible the node in such page
+   * @param page_id
+   * @param id
+   * @return {Poltergeist.Node}
+   */
+  Browser.prototype.node = function (page_id, id) {
+    if (this.currentPage.id === page_id) {
+      return this.currentPage.get(id);
+    } else {
+      throw new Poltergeist.ObsoleteNode;
+    }
+  };
+
+  /**
+   * Returns the frameUrl related to the frame given by name
+   * @param frame_name
+   * @return {*}
+   */
+  Browser.prototype.frameUrl = function (frame_name) {
+    return this.currentPage.frameUrl(frame_name);
+  };
+
+  /**
+   * This method defines the rectangular area of the web page to be rasterized when render is invoked.
+   * If no clipping rectangle is set, render will process the entire web page.
+   * @param full
+   * @param selector
+   * @return {*}
+   */
+  Browser.prototype.set_clip_rect = function (full, selector) {
+    var dimensions, clipDocument, rect, clipViewport;
+
+    dimensions = this.currentPage.validatedDimensions();
+    clipDocument = dimensions.document;
+    clipViewport = dimensions.viewport;
+
+    if (full) {
+      rect = {
+        left: 0,
+        top: 0,
+        width: clipDocument.width,
+        height: clipDocument.height
+      };
+    } else {
+      if (selector != null) {
+        rect = this.currentPage.elementBounds(selector);
+      } else {
+        rect = {
+          left: 0,
+          top: 0,
+          width: clipViewport.width,
+          height: clipViewport.height
+        };
+      }
+    }
+
+    this.currentPage.setClipRect(rect);
+    return dimensions;
+  };
+
+  /**
+   * Kill the browser, i.e kill phantomjs current process
+   * @return {int}
+   */
+  Browser.prototype.exit = function () {
+    return phantom.exit(0);
+  };
+
+  /**
+   * Do nothing
+   */
+  Browser.prototype.noop = function () {
+  };
+
+  /**
+   * Throws a new Object error
+   */
+  Browser.prototype.browser_error = function () {
+    throw new Error('zomg');
+  };
+
+  /**
+   *  Visits a page and load its content
+   * @param serverResponse
+   * @param url
+   * @return {*}
+   */
+  Browser.prototype.visit = function (serverResponse, url) {
+    var prevUrl;
+    var self = this;
+    this.currentPage.state = 'loading';
+    prevUrl = this.currentPage.source === null ? 'about:blank' : this.currentPage.currentUrl();
+    this.currentPage.open(url);
+    if (/#/.test(url) && prevUrl.split('#')[0] === url.split('#')[0]) {
+      this.currentPage.state = 'default';
+      return this.serverSendResponse({
+        status: 'success'
+      }, serverResponse);
+    } else {
+      return this.currentPage.waitState('default', function () {
+        if (self.currentPage.statusCode === null && self.currentPage.status === 'fail') {
+          return self.owner.serverSendError(new Poltergeist.StatusFailError, serverResponse);
+        } else {
+          return self.serverSendResponse({
+            status: self.currentPage.status
+          }, serverResponse);
+        }
+      });
+    }
+  };
+
+  /**
+   *  Puts the control of the browser inside the IFRAME given by name
+   * @param serverResponse
+   * @param name
+   * @param timeout
+   * @return {*}
+   */
+  Browser.prototype.push_frame = function (serverResponse, name, timeout) {
+    var _ref;
+    var self = this;
+
+    if (timeout == null) {
+      timeout = new Date().getTime() + 2000;
+    }
+
+    //TODO: WTF, else if after a if with return COMMON
+    if (_ref = this.frameUrl(name), __indexOf.call(this.currentPage.blockedUrls(), _ref) >= 0) {
+      return this.serverSendResponse(true, serverResponse);
+    } else if (this.currentPage.pushFrame(name)) {
+      if (this.currentPage.currentUrl() === 'about:blank') {
+        this.currentPage.state = 'awaiting_frame_load';
+        return this.currentPage.waitState('default', function () {
+          return self.serverSendResponse(true, serverResponse);
+        });
+      } else {
+        return this.serverSendResponse(true, serverResponse);
+      }
+    } else {
+      if (new Date().getTime() < timeout) {
+        return setTimeout((function () {
+          return self.push_frame(serverResponse, name, timeout);
+        }), 50);
+      } else {
+        return this.owner.serverSendError(new Poltergeist.FrameNotFound(name), serverResponse);
+      }
+    }
+  };
+
+  /**
+   *  Injects a javascript into the current page
+   * @param serverResponse
+   * @param extension
+   * @return {*}
+   */
+  Browser.prototype.add_extension = function (serverResponse, extension) {
+    //TODO: error control when the injection was not possible
+    this.currentPage.injectExtension(extension);
+    return this.serverSendResponse('success', serverResponse);
+  };
+
+  /**
+   *  Returns the url we are currently in
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.current_url = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.currentUrl(), serverResponse);
+  };
+
+  /**
+   *  Returns the current page window name
+   * @param serverResponse
+   * @returns {*}
+   */
+  Browser.prototype.window_name = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.windowName(), serverResponse);
+  };
+
+  /**
+   *  Returns the status code associated to the page
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.status_code = function (serverResponse) {
+    if (this.currentPage.statusCode === undefined || this.currentPage.statusCode === null) {
+      return this.owner.serverSendError(new Poltergeist.StatusFailError("status_code_error"), serverResponse);
+    }
+    return this.serverSendResponse(this.currentPage.statusCode, serverResponse);
+  };
+
+  /**
+   *  Returns the source code of the active frame, useful for when inside an IFRAME
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.body = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.content(), serverResponse);
+  };
+
+  /**
+   * Returns the source code of the page all the html
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.source = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.source, serverResponse);
+  };
+
+  /**
+   * Returns the current page title
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.title = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.title(), serverResponse);
+  };
+
+  /**
+   *  Finds the elements that match a method of selection and a selector
+   * @param serverResponse
+   * @param method
+   * @param selector
+   * @return {*}
+   */
+  Browser.prototype.find = function (serverResponse, method, selector) {
+    return this.serverSendResponse({
+      page_id: this.currentPage.id,
+      ids: this.currentPage.find(method, selector)
+    }, serverResponse);
+  };
+
+  /**
+   * Find elements within a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param method
+   * @param selector
+   * @return {*}
+   */
+  Browser.prototype.find_within = function (serverResponse, page_id, id, method, selector) {
+    return this.serverSendResponse(this.node(page_id, id).find(method, selector), serverResponse);
+  };
+
+  /**
+   * Returns ALL the text, visible and not visible from the given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.all_text = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).allText(), serverResponse);
+  };
+
+  /**
+   * Returns the inner or outer html of a given id
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param type
+   * @returns Object
+   */
+  Browser.prototype.all_html = function (serverResponse, page_id, id, type) {
+    return this.serverSendResponse(this.node(page_id, id).allHTML(type), serverResponse);
+  };
+
+  /**
+   *  Returns only the visible text in a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.visible_text = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).visibleText(), serverResponse);
+  };
+
+  /**
+   * Deletes the text in a given element leaving it empty
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.delete_text = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).deleteText(), serverResponse);
+  };
+
+  /**
+   *  Gets the value of a given attribute in an element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param name
+   * @return {*}
+   */
+  Browser.prototype.attribute = function (serverResponse, page_id, id, name) {
+    return this.serverSendResponse(this.node(page_id, id).getAttribute(name), serverResponse);
+  };
+
+  /**
+   *  Allows the possibility to set an attribute on a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param name
+   * @param value
+   * @returns {*}
+   */
+  Browser.prototype.set_attribute = function (serverResponse, page_id, id, name, value) {
+    return this.serverSendResponse(this.node(page_id, id).setAttribute(name, value), serverResponse);
+  };
+
+  /**
+   *  Allows the possibility to remove an attribute on a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param name
+   * @returns {*}
+   */
+  Browser.prototype.remove_attribute = function (serverResponse, page_id, id, name) {
+    return this.serverSendResponse(this.node(page_id, id).removeAttribute(name), serverResponse);
+  };
+
+  /**
+   * Returns all the attributes of a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param name
+   * @return {*}
+   */
+  Browser.prototype.attributes = function (serverResponse, page_id, id, name) {
+    return this.serverSendResponse(this.node(page_id, id).getAttributes(), serverResponse);
+  };
+
+  /**
+   *  Returns all the way to the document level the parents of a given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.parents = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).parentIds(), serverResponse);
+  };
+
+  /**
+   * Returns the element.value of an element given by its page and id
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.value = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).value(), serverResponse);
+  };
+
+  /**
+   *  Sets the element.value of an element by the given value
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param value
+   * @return {*}
+   */
+  Browser.prototype.set = function (serverResponse, page_id, id, value) {
+    this.node(page_id, id).set(value);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   *  Uploads a file to an input file element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param file_path
+   * @return {*}
+   */
+  Browser.prototype.select_file = function (serverResponse, page_id, id, file_path) {
+    var node = this.node(page_id, id);
+
+    this.currentPage.beforeUpload(node.id);
+    this.currentPage.uploadFile('[_poltergeist_selected]', file_path);
+    this.currentPage.afterUpload(node.id);
+
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Sets a value to the selected element (to be used in select elements)
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param value
+   * @return {*}
+   */
+  Browser.prototype.select = function (serverResponse, page_id, id, value) {
+    return this.serverSendResponse(this.node(page_id, id).select(value), serverResponse);
+  };
+
+  /**
+   *  Selects an option with the given value
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param value
+   * @param multiple
+   * @return {*}
+   */
+  Browser.prototype.select_option = function (serverResponse, page_id, id, value, multiple) {
+    return this.serverSendResponse(this.node(page_id, id).select_option(value, multiple), serverResponse);
+  };
+
+  /**
+   *
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.tag_name = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).tagName(), serverResponse);
+  };
+
+
+  /**
+   * Tells if an element is visible or not
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.visible = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).isVisible(), serverResponse);
+  };
+
+  /**
+   *  Tells if an element is disabled
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.disabled = function (serverResponse, page_id, id) {
+    return this.serverSendResponse(this.node(page_id, id).isDisabled(), serverResponse);
+  };
+
+  /**
+   *  Evaluates a javascript and returns the outcome to the client
+   *  This will be JSON response so your script better be returning objects that can be used
+   *  in JSON.stringify
+   * @param serverResponse
+   * @param script
+   * @return {*}
+   */
+  Browser.prototype.evaluate = function (serverResponse, script) {
+    return this.serverSendResponse(this.currentPage.evaluate("function() { return " + script + " }"), serverResponse);
+  };
+
+  /**
+   *  Executes a javascript and goes back to the client with true if there were no errors
+   * @param serverResponse
+   * @param script
+   * @return {*}
+   */
+  Browser.prototype.execute = function (serverResponse, script) {
+    this.currentPage.execute("function() { " + script + " }");
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * If inside a frame then we will go back to the parent
+   * Not defined behaviour if you pop and are not inside an iframe
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.pop_frame = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.popFrame(), serverResponse);
+  };
+
+  /**
+   * Gets the window handle id by a given window name
+   * @param serverResponse
+   * @param name
+   * @return {*}
+   */
+  Browser.prototype.window_handle = function (serverResponse, name) {
+    var handle, pageByWindowName;
+
+    if (name === null || typeof name == "undefined" || name.length === 0) {
+      return this.serverSendResponse(this.currentPage.handle, serverResponse);
+    }
+
+    handle = null;
+
+    //Lets search the handle by the given window name
+    var filteredPages = this.pages.filter(function (p) {
+      return !p.closed && p.windowName() === name;
+    });
+
+    //A bit of error control is always good
+    if (Array.isArray(filteredPages) && filteredPages.length >= 1) {
+      pageByWindowName = filteredPages[0];
+    } else {
+      pageByWindowName = null;
+    }
+
+    if (pageByWindowName !== null && typeof pageByWindowName != "undefined") {
+      handle = pageByWindowName.handle;
+    }
+
+    return this.serverSendResponse(handle, serverResponse);
+  };
+
+  /**
+   * Returns all the window handles of opened windows
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.window_handles = function (serverResponse) {
+    var handles, filteredPages;
+
+    filteredPages = this.pages.filter(function (p) {
+      return !p.closed;
+    });
+
+    if (filteredPages.length > 0) {
+      handles = filteredPages.map(function (p) {
+        return p.handle;
+      });
+      if (handles.length === 0) {
+        handles = null;
+      }
+    } else {
+      handles = null;
+    }
+
+    return this.serverSendResponse(handles, serverResponse);
+  };
+
+  /**
+   *  Tries to switch to a window given by the handle id
+   * @param serverResponse
+   * @param handle
+   * @return {*}
+   */
+  Browser.prototype.switch_to_window = function (serverResponse, handle) {
+    var page;
+    var self = this;
+
+    page = this.getPageByHandle(handle);
+    if (page === null || typeof page == "undefined") {
+      throw new Poltergeist.NoSuchWindowError;
+    }
+
+    if (page !== this.currentPage) {
+      return page.waitState('default', function () {
+        self.currentPage = page;
+        return self.serverSendResponse(true, serverResponse);
+      });
+    }
+
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Opens a new window where we can do stuff
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.open_new_window = function (serverResponse) {
+    return this.execute(serverResponse, 'window.open()');
+  };
+
+  /**
+   * Closes the window given by handle name if possible
+   * @param serverResponse
+   * @param handle
+   * @return {*}
+   */
+  Browser.prototype.close_window = function (serverResponse, handle) {
+    var page;
+
+    page = this.getPageByHandle(handle);
+    if (page === null || typeof  page == "undefined") {
+      //TODO: should we throw error since we actually could not find the window?
+      return this.serverSendResponse(false, serverResponse);
+    }
+
+    //TODO: we have to add some control here to actually asses that the release has been done
+    page.release();
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Generic mouse event on an element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param name
+   * @return {number}
+   */
+  Browser.prototype.mouse_event = function (serverResponse, page_id, id, name) {
+    var node;
+    var self = this;
+    node = this.node(page_id, id);
+    this.currentPage.state = 'mouse_event';
+    this.last_mouse_event = node.mouseEvent(name);
+    return setTimeout(function () {
+      if (self.currentPage.state === 'mouse_event') {
+        self.currentPage.state = 'default';
+        return self.serverSendResponse({
+          position: self.last_mouse_event
+        }, serverResponse);
+      } else {
+        return self.currentPage.waitState('default', function () {
+          return self.serverSendResponse({
+            position: self.last_mouse_event
+          }, serverResponse);
+        });
+      }
+    }, 5);
+  };
+
+  /**
+   * Simple click on the element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.click = function (serverResponse, page_id, id) {
+    return this.mouse_event(serverResponse, page_id, id, 'click');
+  };
+
+  /**
+   * Right click on the element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.right_click = function (serverResponse, page_id, id) {
+    return this.mouse_event(serverResponse, page_id, id, 'rightclick');
+  };
+
+  /**
+   *  Double click on the element given by page and id
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.double_click = function (serverResponse, page_id, id) {
+    return this.mouse_event(serverResponse, page_id, id, 'doubleclick');
+  };
+
+  /**
+   * Executes a mousemove event on the page and given element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @return {*}
+   */
+  Browser.prototype.hover = function (serverResponse, page_id, id) {
+    return this.mouse_event(serverResponse, page_id, id, 'mousemove');
+  };
+
+  /**
+   * Triggers a mouse click event on the given coordinates
+   * @param serverResponse
+   * @param x
+   * @param y
+   * @return {*}
+   */
+  Browser.prototype.click_coordinates = function (serverResponse, x, y) {
+    var response;
+
+    this.currentPage.sendEvent('click', x, y);
+    response = {
+      click: {
+        x: x,
+        y: y
+      }
+    };
+
+    return this.serverSendResponse(response, serverResponse);
+  };
+
+  /**
+   *  Drags one element into another, useful for nice javascript thingies
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param other_id
+   * @return {*}
+   */
+  Browser.prototype.drag = function (serverResponse, page_id, id, other_id) {
+    this.node(page_id, id).dragTo(this.node(page_id, other_id));
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Triggers an event on the given page and element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param event
+   * @return {*}
+   */
+  Browser.prototype.trigger = function (serverResponse, page_id, id, event) {
+    this.node(page_id, id).trigger(event);
+    return this.serverSendResponse(event, serverResponse);
+  };
+
+  /**
+   * Checks if two elements are equal on a dom level
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param other_id
+   * @return {*}
+   */
+  Browser.prototype.equals = function (serverResponse, page_id, id, other_id) {
+    return this.serverSendResponse(this.node(page_id, id).isEqual(this.node(page_id, other_id)), serverResponse);
+  };
+
+  /**
+   * Resets the current page to a clean slate
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.reset = function (serverResponse) {
+    this.resetPage();
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Scrolls to a position given by the left, top coordinates
+   * @param serverResponse
+   * @param left
+   * @param top
+   * @return {*}
+   */
+  Browser.prototype.scroll_to = function (serverResponse, left, top) {
+    this.currentPage.setScrollPosition({
+      left: left,
+      top: top
+    });
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Sends keys to an element simulating as closest as possible what a user would do
+   * when typing
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param keys
+   * @return {*}
+   */
+  Browser.prototype.send_keys = function (serverResponse, page_id, id, keys) {
+    var key, sequence, target, _i, _len;
+    target = this.node(page_id, id);
+    if (!target.containsSelection()) {
+      target.mouseEvent('click');
+    }
+    for (_i = 0, _len = keys.length; _i < _len; _i++) {
+      sequence = keys[_i];
+      key = sequence.key != null ? this.currentPage.keyCode(sequence.key) : sequence;
+      this.currentPage.sendEvent('keypress', key);
+    }
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Sends a native phantomjs key event to element
+   * @param serverResponse
+   * @param page_id
+   * @param id
+   * @param keyEvent
+   * @param key
+   * @param modifier
+   */
+  Browser.prototype.key_event = function (serverResponse, page_id, id, keyEvent, key, modifier) {
+    var keyEventModifierMap;
+    var keyEventModifier;
+    var target;
+
+    keyEventModifierMap = {
+      'none': 0x0,
+      'shift': 0x02000000,
+      'ctrl': 0x04000000,
+      'alt': 0x08000000,
+      'meta': 0x10000000
+    };
+    keyEventModifier = keyEventModifierMap[modifier];
+
+    target = this.node(page_id, id);
+    if (!target.containsSelection()) {
+      target.mouseEvent('click');
+    }
+    target.page.sendEvent(keyEvent, key, null, null, keyEventModifier);
+
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   *  Sends the rendered page in a base64 encoding
+   * @param serverResponse
+   * @param format
+   * @param full
+   * @param selector
+   * @return {*}
+   */
+  Browser.prototype.render_base64 = function (serverResponse, format, full, selector) {
+    var encoded_image;
+    if (selector == null) {
+      selector = null;
+    }
+    this.set_clip_rect(full, selector);
+    encoded_image = this.currentPage.renderBase64(format);
+    return this.serverSendResponse(encoded_image, serverResponse);
+  };
+
+  /**
+   * Renders the current page entirely or a given selection
+   * @param serverResponse
+   * @param path
+   * @param full
+   * @param selector
+   * @return {*}
+   */
+  Browser.prototype.render = function (serverResponse, path, full, selector) {
+    var dimensions;
+    if (selector == null) {
+      selector = null;
+    }
+    dimensions = this.set_clip_rect(full, selector);
+    this.currentPage.setScrollPosition({
+      left: 0,
+      top: 0
+    });
+    this.currentPage.render(path);
+    this.currentPage.setScrollPosition({
+      left: dimensions.left,
+      top: dimensions.top
+    });
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+
+  /**
+   * Sets the paper size, useful when printing to PDF
+   * @param serverResponse
+   * @param size
+   * @return {*}
+   */
+  Browser.prototype.set_paper_size = function (serverResponse, size) {
+    this.currentPage.setPaperSize(size);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   *  Sets the zoom factor on the current page
+   * @param serverResponse
+   * @param zoom_factor
+   * @return {*}
+   */
+  Browser.prototype.set_zoom_factor = function (serverResponse, zoom_factor) {
+    this.currentPage.setZoomFactor(zoom_factor);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Resizes the browser viewport, useful when testing mobile stuff
+   * @param serverResponse
+   * @param width
+   * @param height
+   * @return {*}
+   */
+  Browser.prototype.resize = function (serverResponse, width, height) {
+    this.currentPage.setViewportSize({
+      width: width,
+      height: height
+    });
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Gets the browser viewport size
+   * Because PhantomJS is headless (nothing is shown)
+   * viewportSize effectively simulates the size of the window like in a traditional browser.
+   * @param serverResponse
+   * @param handle
+   * @return {*}
+   */
+  Browser.prototype.window_size = function (serverResponse, handle) {
+    //TODO: add support for window handles
+    return this.serverSendResponse(this.currentPage.viewportSize(), serverResponse);
+  };
+
+  /**
+   * Returns the network traffic that the current page has generated
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.network_traffic = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.networkTraffic(), serverResponse);
+  };
+
+  /**
+   * Clears the accumulated network_traffic in the current page
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.clear_network_traffic = function (serverResponse) {
+    this.currentPage.clearNetworkTraffic();
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Gets the headers of the current page
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.get_headers = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.getCustomHeaders(), serverResponse);
+  };
+
+  /**
+   * Set headers in the browser
+   * @param serverResponse
+   * @param headers
+   * @return {*}
+   */
+  Browser.prototype.set_headers = function (serverResponse, headers) {
+    if (headers['User-Agent']) {
+      this.currentPage.setUserAgent(headers['User-Agent']);
+    }
+    this.currentPage.setCustomHeaders(headers);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Given an array of headers, adds them to the page
+   * @param serverResponse
+   * @param headers
+   * @return {*}
+   */
+  Browser.prototype.add_headers = function (serverResponse, headers) {
+    var allHeaders, name, value;
+    allHeaders = this.currentPage.getCustomHeaders();
+    for (name in headers) {
+      if (headers.hasOwnProperty(name)) {
+        value = headers[name];
+        allHeaders[name] = value;
+      }
+    }
+    return this.set_headers(serverResponse, allHeaders);
+  };
+
+  /**
+   * Adds a header to the page temporary or permanently
+   * @param serverResponse
+   * @param header
+   * @param permanent
+   * @return {*}
+   */
+  Browser.prototype.add_header = function (serverResponse, header, permanent) {
+    if (!permanent) {
+      this.currentPage.addTempHeader(header);
+    }
+    return this.add_headers(serverResponse, header);
+  };
+
+
+  /**
+   * Sends back the client the response headers sent from the browser when making
+   * the page request
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.response_headers = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.responseHeaders(), serverResponse);
+  };
+
+  /**
+   * Returns the cookies of the current page being browsed
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.cookies = function (serverResponse) {
+    return this.serverSendResponse(this.currentPage.cookies(), serverResponse);
+  };
+
+  /**
+   * Sets a cookie in the browser, the format of the cookies has to be the format it says
+   * on phantomjs documentation and as such you can set it in other domains, not on the
+   * current page
+   * @param serverResponse
+   * @param cookie
+   * @return {*}
+   */
+  Browser.prototype.set_cookie = function (serverResponse, cookie) {
+    return this.serverSendResponse(phantom.addCookie(cookie), serverResponse);
+  };
+
+  /**
+   * Remove a cookie set on the current page
+   * @param serverResponse
+   * @param name
+   * @return {*}
+   */
+  Browser.prototype.remove_cookie = function (serverResponse, name) {
+    //TODO: add error control to check if the cookie was properly deleted
+    this.currentPage.deleteCookie(name);
+    phantom.deleteCookie(name);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Clear the cookies in the browser
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.clear_cookies = function (serverResponse) {
+    phantom.clearCookies();
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Enables / Disables the cookies on the browser
+   * @param serverResponse
+   * @param flag
+   * @return {*}
+   */
+  Browser.prototype.cookies_enabled = function (serverResponse, flag) {
+    phantom.cookiesEnabled = flag;
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * US19: DONE
+   * Sets a basic authentication credential to access a page
+   * THIS SHOULD BE USED BEFORE accessing a page
+   * @param serverResponse
+   * @param user
+   * @param password
+   * @return {*}
+   */
+  Browser.prototype.set_http_auth = function (serverResponse, user, password) {
+    this.currentPage.setHttpAuth(user, password);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Sets the flag whether to fail on javascript errors or not.
+   * @param serverResponse
+   * @param value
+   * @return {*}
+   */
+  Browser.prototype.set_js_errors = function (serverResponse, value) {
+    this.js_errors = value;
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Sets the debug mode to boolean value
+   * @param serverResponse
+   * @param value
+   * @return {*}
+   */
+  Browser.prototype.set_debug = function (serverResponse, value) {
+    this._debug = value;
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Goes back in the history when possible
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.go_back = function (serverResponse) {
+    var self = this;
+    if (this.currentPage.canGoBack()) {
+      this.currentPage.state = 'loading';
+      this.currentPage.goBack();
+      return this.currentPage.waitState('default', function () {
+        return self.serverSendResponse(true, serverResponse);
+      });
+    } else {
+      return this.serverSendResponse(false, serverResponse);
+    }
+  };
+
+  /**
+   * Reloads the page if possible
+   * @return {*}
+   */
+  Browser.prototype.reload = function (serverResponse) {
+    var self = this;
+    this.currentPage.state = 'loading';
+    this.currentPage.reload();
+    return this.currentPage.waitState('default', function () {
+      return self.serverSendResponse(true, serverResponse);
+    });
+  };
+
+  /**
+   * Goes forward in the browser history if possible
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.go_forward = function (serverResponse) {
+    var self = this;
+    if (this.currentPage.canGoForward()) {
+      this.currentPage.state = 'loading';
+      this.currentPage.goForward();
+      return this.currentPage.waitState('default', function () {
+        return self.serverSendResponse(true, serverResponse);
+      });
+    } else {
+      return this.serverSendResponse(false, serverResponse);
+    }
+  };
+
+  /**
+   *  Sets the urlBlacklist for the given urls as parameters
+   * @return {boolean}
+   */
+  Browser.prototype.set_url_blacklist = function (serverResponse, blackList) {
+    this.currentPage.urlBlacklist = Array.prototype.slice.call(blackList);
+    return this.serverSendResponse(true, serverResponse);
+  };
+
+  /**
+   * Runs a browser command and returns the response back to the client
+   * when the command has finished the execution
+   * @param command
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.serverRunCommand = function (command, serverResponse) {
+    var commandData;
+    var commandArgs;
+    var commandName;
+
+    commandName = command.name;
+    commandArgs = command.args;
+    this.currentPage.state = 'default';
+    commandData = [serverResponse].concat(commandArgs);
+
+    if (typeof this[commandName] !== "function") {
+      //We can not run such command
+      throw new Poltergeist.Error();
+    }
+
+    return this[commandName].apply(this, commandData);
+  };
+
+  /**
+   * Sends a response back to the client who made the request
+   * @param response
+   * @param serverResponse
+   * @return {*}
+   */
+  Browser.prototype.serverSendResponse = function (response, serverResponse) {
+    var errors;
+    errors = this.currentPage.errors;
+    this.currentPage.clearErrors();
+    if (errors.length > 0 && this.js_errors) {
+      return this.owner.serverSendError(new Poltergeist.JavascriptError(errors), serverResponse);
+    } else {
+      return this.owner.serverSendResponse(response, serverResponse);
+    }
+  };
+
+  return Browser;
+
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/main.js b/vendor/jcalderonzumba/gastonjs/src/Client/main.js
new file mode 100644
index 0000000..a8f2ecb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/main.js
@@ -0,0 +1,29 @@
+var Poltergeist, system, _ref, _ref1, _ref2;
+
+//Inheritance tool
+phantom.injectJs("" + phantom.libraryPath + "/Tools/inherit.js");
+
+//Poltergeist main object
+phantom.injectJs("" + phantom.libraryPath + "/poltergeist.js");
+
+//Errors that are controller in the poltergeist code
+phantom.injectJs("" + phantom.libraryPath + "/Errors/error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/obsolete_node.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/invalid_selector.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/frame_not_found.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/mouse_event_failed.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/javascript_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/browser_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/status_fail_error.js");
+phantom.injectJs("" + phantom.libraryPath + "/Errors/no_such_window_error.js");
+
+//web server to control the commands
+phantom.injectJs("" + phantom.libraryPath + "/Server/server.js");
+
+phantom.injectJs("" + phantom.libraryPath + "/web_page.js");
+phantom.injectJs("" + phantom.libraryPath + "/node.js");
+phantom.injectJs("" + phantom.libraryPath + "/browser.js");
+
+system = require('system');
+
+new Poltergeist(system.args[1], system.args[2], system.args[3]);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/node.js b/vendor/jcalderonzumba/gastonjs/src/Client/node.js
new file mode 100644
index 0000000..bdf5baf
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/node.js
@@ -0,0 +1,161 @@
+var __slice = [].slice;
+
+Poltergeist.Node = (function () {
+  var name, _fn, _i, _len, _ref;
+  var xpathStringLiteral;
+
+  Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'checked',
+    'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple',
+    'select', 'tagName', 'find', 'getAttributes', 'isVisible',
+    'position', 'trigger', 'input', 'parentId', 'parentIds', 'mouseEventTest',
+    'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'selectRadioValue',
+    'containsSelection', 'allHTML', 'changed', 'getXPathForElement', 'deselectAllOptions'];
+
+  function Node(page, id) {
+    this.page = page;
+    this.id = id;
+  }
+
+  /**
+   * Returns the parent Node of this Node
+   * @return {Poltergeist.Node}
+   */
+  Node.prototype.parent = function () {
+    return new Poltergeist.Node(this.page, this.parentId());
+  };
+
+  _ref = Node.DELEGATES;
+
+  _fn = function (name) {
+    return Node.prototype[name] = function () {
+      var args = [];
+      if (arguments.length >= 1) {
+        args = __slice.call(arguments, 0)
+      }
+      return this.page.nodeCall(this.id, name, args);
+    };
+  };
+
+  //Adding all the delegates from the agent Node to this Node
+  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+    name = _ref[_i];
+    _fn(name);
+  }
+
+  xpathStringLiteral = function (s) {
+    if (s.indexOf('"') === -1)
+      return '"' + s + '"';
+    if (s.indexOf("'") === -1)
+      return "'" + s + "'";
+    return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
+  };
+
+  /**
+   *  Gets an x,y position tailored for mouse event actions
+   * @return {{x, y}}
+   */
+  Node.prototype.mouseEventPosition = function () {
+    var middle, pos, viewport;
+
+    viewport = this.page.viewportSize();
+    pos = this.position();
+    middle = function (start, end, size) {
+      return start + ((Math.min(end, size) - start) / 2);
+    };
+
+    return {
+      x: middle(pos.left, pos.right, viewport.width),
+      y: middle(pos.top, pos.bottom, viewport.height)
+    };
+  };
+
+  /**
+   * Executes a phantomjs native mouse event
+   * @param name
+   * @return {{x, y}}
+   */
+  Node.prototype.mouseEvent = function (name) {
+    var pos, test;
+
+    this.scrollIntoView();
+    pos = this.mouseEventPosition();
+    test = this.mouseEventTest(pos.x, pos.y);
+
+    if (test.status === 'success') {
+      if (name === 'rightclick') {
+        this.page.mouseEvent('click', pos.x, pos.y, 'right');
+        this.trigger('contextmenu');
+      } else {
+        this.page.mouseEvent(name, pos.x, pos.y);
+      }
+      return pos;
+    } else {
+      throw new Poltergeist.MouseEventFailed(name, test.selector, pos);
+    }
+  };
+
+  /**
+   * Executes a mouse based drag from one node to another
+   * @param other
+   * @return {{x, y}}
+   */
+  Node.prototype.dragTo = function (other) {
+    var otherPosition, position;
+
+    this.scrollIntoView();
+    position = this.mouseEventPosition();
+    otherPosition = other.mouseEventPosition();
+    this.page.mouseEvent('mousedown', position.x, position.y);
+    return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y);
+  };
+
+  /**
+   * Checks if one node is equal to another
+   * @param other
+   * @return {boolean}
+   */
+  Node.prototype.isEqual = function (other) {
+    return this.page === other.page && this.isDOMEqual(other.id);
+  };
+
+
+  /**
+   * The value to select
+   * @param value
+   * @param multiple
+   */
+  Node.prototype.select_option = function (value, multiple) {
+    var tagName = this.tagName().toLowerCase();
+
+    if (tagName === "select") {
+      var escapedOption = xpathStringLiteral(value);
+      // 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 = this.find("xpath", optionQuery);
+      var polterNode = this.page.get(ids[0]);
+
+      if (multiple || !this.getAttribute('multiple')) {
+        if (!polterNode.getAttribute('selected')) {
+          polterNode.select(value);
+          this.trigger('click');
+          this.input();
+        }
+        return true;
+      }
+
+      this.deselectAllOptions();
+      polterNode.select(value);
+      this.trigger('click');
+      this.input();
+      return true;
+    } else if (tagName === "input" && this.getAttribute("type").toLowerCase() === "radio") {
+      return this.selectRadioValue(value);
+    }
+
+    throw new Poltergeist.BrowserError("The element is not a select or radio input");
+
+  };
+
+  return Node;
+
+}).call(this);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js b/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js
new file mode 100644
index 0000000..20f0267
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/poltergeist.js
@@ -0,0 +1,77 @@
+Poltergeist = (function () {
+
+  /**
+   * The MAIN class of the project
+   * @param port
+   * @param width
+   * @param height
+   * @constructor
+   */
+  function Poltergeist(port, width, height) {
+    var self;
+    this.browser = new Poltergeist.Browser(this, width, height);
+
+    this.commandServer = new Poltergeist.Server(this, port);
+    this.commandServer.start();
+
+    self = this;
+
+    phantom.onError = function (message, stack) {
+      return self.onError(message, stack);
+    };
+
+    this.running = false;
+  }
+
+  /**
+   * Tries to execute a command send by a client and returns the command response
+   * or error if something happened
+   * @param command
+   * @param serverResponse
+   * @return {boolean}
+   */
+  Poltergeist.prototype.serverRunCommand = function (command, serverResponse) {
+    var error;
+    this.running = true;
+    try {
+      return this.browser.serverRunCommand(command, serverResponse);
+    } catch (_error) {
+      error = _error;
+      if (error instanceof Poltergeist.Error) {
+        return this.serverSendError(error, serverResponse);
+      }
+      return this.serverSendError(new Poltergeist.BrowserError(error.toString(), error.stack), serverResponse);
+    }
+  };
+
+  /**
+   * Sends error back to the client
+   * @param error
+   * @param serverResponse
+   * @return {boolean}
+   */
+  Poltergeist.prototype.serverSendError = function (error, serverResponse) {
+    var errorObject;
+    errorObject = {
+      error: {
+        name: error.name || 'Generic',
+        args: error.args && error.args() || [error.toString()]
+      }
+    };
+    return this.commandServer.sendError(serverResponse, 500, errorObject);
+  };
+
+  /**
+   * Send the response back to the client
+   * @param response        Data to send to the client
+   * @param serverResponse  Phantomjs response object associated to the client request
+   * @return {boolean}
+   */
+  Poltergeist.prototype.serverSendResponse = function (response, serverResponse) {
+    return this.commandServer.send(serverResponse, {response: response});
+  };
+
+  return Poltergeist;
+})();
+
+window.Poltergeist = Poltergeist;
diff --git a/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js b/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js
new file mode 100644
index 0000000..c275b03
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Client/web_page.js
@@ -0,0 +1,829 @@
+var __slice = [].slice;
+var __indexOf = [].indexOf || function (item) {
+    for (var i = 0, l = this.length; i < l; i++) {
+      if (i in this && this[i] === item) return i;
+    }
+    return -1;
+  };
+
+Poltergeist.WebPage = (function () {
+  var command, delegate, commandFunctionBind, delegateFunctionBind, i, j, commandsLength, delegatesRefLength, commandsRef, delegatesRef,
+    _this = this;
+
+  //Native or not webpage callbacks
+  WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested',
+    'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
+
+  // Delegates the execution to the phantomjs page native functions but directly available in the WebPage object
+  WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward', 'reload'];
+
+  //Commands to execute on behalf of the browser but on the current page
+  WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage'];
+
+  WebPage.EXTENSIONS = [];
+
+  function WebPage(nativeWebPage) {
+    var callback, i, callBacksLength, callBacksRef;
+
+    //Lets create the native phantomjs webpage
+    if (nativeWebPage === null || typeof nativeWebPage == "undefined") {
+      this._native = require('webpage').create();
+    } else {
+      this._native = nativeWebPage;
+    }
+
+    this.id = 0;
+    this.source = null;
+    this.closed = false;
+    this.state = 'default';
+    this.urlBlacklist = [];
+    this.frames = [];
+    this.errors = [];
+    this._networkTraffic = {};
+    this._tempHeaders = {};
+    this._blockedUrls = [];
+
+    callBacksRef = WebPage.CALLBACKS;
+    for (i = 0, callBacksLength = callBacksRef.length; i < callBacksLength; i++) {
+      callback = callBacksRef[i];
+      this.bindCallback(callback);
+    }
+  }
+
+  //Bind the commands we can run from the browser to the current page
+  commandsRef = WebPage.COMMANDS;
+  commandFunctionBind = function (command) {
+    return WebPage.prototype[command] = function () {
+      var args;
+      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
+      return this.runCommand(command, args);
+    };
+  };
+  for (i = 0, commandsLength = commandsRef.length; i < commandsLength; i++) {
+    command = commandsRef[i];
+    commandFunctionBind(command);
+  }
+
+  //Delegates bind applications
+  delegatesRef = WebPage.DELEGATES;
+  delegateFunctionBind = function (delegate) {
+    return WebPage.prototype[delegate] = function () {
+      return this._native[delegate].apply(this._native, arguments);
+    };
+  };
+  for (j = 0, delegatesRefLength = delegatesRef.length; j < delegatesRefLength; j++) {
+    delegate = delegatesRef[j];
+    delegateFunctionBind(delegate);
+  }
+
+  /**
+   * This callback is invoked after the web page is created but before a URL is loaded.
+   * The callback may be used to change global objects.
+   * @return {*}
+   */
+  WebPage.prototype.onInitializedNative = function () {
+    this.id += 1;
+    this.source = null;
+    this.injectAgent();
+    this.removeTempHeaders();
+    return this.setScrollPosition({
+      left: 0,
+      top: 0
+    });
+  };
+
+  /**
+   * This callback is invoked when the WebPage object is being closed,
+   * either via page.close in the PhantomJS outer space or via window.close in the page's client-side.
+   * @return {boolean}
+   */
+  WebPage.prototype.onClosingNative = function () {
+    this.handle = null;
+    return this.closed = true;
+  };
+
+  /**
+   * This callback is invoked when there is a JavaScript console message on the web page.
+   * The callback may accept up to three arguments: the string for the message, the line number, and the source identifier.
+   * @param message
+   * @param line
+   * @param sourceId
+   * @return {boolean}
+   */
+  WebPage.prototype.onConsoleMessageNative = function (message, line, sourceId) {
+    if (message === '__DOMContentLoaded') {
+      this.source = this._native.content;
+      return false;
+    }
+    console.log(message);
+    return true;
+  };
+
+  /**
+   * This callback is invoked when the page starts the loading. There is no argument passed to the callback.
+   * @return {number}
+   */
+  WebPage.prototype.onLoadStartedNative = function () {
+    this.state = 'loading';
+    return this.requestId = this.lastRequestId;
+  };
+
+  /**
+   * This callback is invoked when the page finishes the loading.
+   * It may accept a single argument indicating the page's status: 'success' if no network errors occurred, otherwise 'fail'.
+   * @param status
+   * @return {string}
+   */
+  WebPage.prototype.onLoadFinishedNative = function (status) {
+    this.status = status;
+    this.state = 'default';
+
+    if (this.source === null || typeof this.source == "undefined") {
+      this.source = this._native.content;
+    } else {
+      this.source = this._native.content;
+    }
+
+    return this.source;
+  };
+
+  /**
+   * This callback is invoked when there is a JavaScript execution error.
+   * It is a good way to catch problems when evaluating a script in the web page context.
+   * The arguments passed to the callback are the error message and the stack trace [as an Array].
+   * @param message
+   * @param stack
+   * @return {Number}
+   */
+  WebPage.prototype.onErrorNative = function (message, stack) {
+    var stackString;
+
+    stackString = message;
+    stack.forEach(function (frame) {
+      stackString += "\n";
+      stackString += "    at " + frame.file + ":" + frame.line;
+      if (frame["function"] && frame["function"] !== '') {
+        return stackString += " in " + frame["function"];
+      }
+    });
+
+    return this.errors.push({
+      message: message,
+      stack: stackString
+    });
+  };
+
+  /**
+   * This callback is invoked when the page requests a resource.
+   * The first argument to the callback is the requestData metadata object.
+   * The second argument is the networkRequest object itself.
+   * @param requestData
+   * @param networkRequest
+   * @return {*}
+   */
+  WebPage.prototype.onResourceRequestedNative = function (requestData, networkRequest) {
+    var abort;
+
+    abort = this.urlBlacklist.some(function (blacklistedUrl) {
+      return requestData.url.indexOf(blacklistedUrl) !== -1;
+    });
+
+    if (abort) {
+      if (this._blockedUrls.indexOf(requestData.url) === -1) {
+        this._blockedUrls.push(requestData.url);
+      }
+      //TODO: check this, as it raises onResourceError
+      return networkRequest.abort();
+    }
+
+    this.lastRequestId = requestData.id;
+    if (requestData.url === this.redirectURL) {
+      this.redirectURL = null;
+      this.requestId = requestData.id;
+    }
+
+    return this._networkTraffic[requestData.id] = {
+      request: requestData,
+      responseParts: []
+    };
+  };
+
+  /**
+   * This callback is invoked when a resource requested by the page is received.
+   * The only argument to the callback is the response metadata object.
+   * @param response
+   * @return {*}
+   */
+  WebPage.prototype.onResourceReceivedNative = function (response) {
+    var networkTrafficElement;
+
+    if ((networkTrafficElement = this._networkTraffic[response.id]) != null) {
+      networkTrafficElement.responseParts.push(response);
+    }
+
+    if (this.requestId === response.id) {
+      if (response.redirectURL) {
+        return this.redirectURL = response.redirectURL;
+      }
+
+      this.statusCode = response.status;
+      return this._responseHeaders = response.headers;
+    }
+  };
+
+  /**
+   * Inject the poltergeist agent into the webpage
+   * @return {Array}
+   */
+  WebPage.prototype.injectAgent = function () {
+    var extension, isAgentInjected, i, extensionsRefLength, extensionsRef, injectionResults;
+
+    isAgentInjected = this["native"]().evaluate(function () {
+      return typeof window.__poltergeist;
+    });
+
+    if (isAgentInjected === "undefined") {
+      this["native"]().injectJs("" + phantom.libraryPath + "/agent.js");
+      extensionsRef = WebPage.EXTENSIONS;
+      injectionResults = [];
+      for (i = 0, extensionsRefLength = extensionsRef.length; i < extensionsRefLength; i++) {
+        extension = extensionsRef[i];
+        injectionResults.push(this["native"]().injectJs(extension));
+      }
+      return injectionResults;
+    }
+  };
+
+  /**
+   * Injects a Javascript file extension into the
+   * @param file
+   * @return {*}
+   */
+  WebPage.prototype.injectExtension = function (file) {
+    //TODO: add error control, for example, check if file already in the extensions array, check if the file exists, etc.
+    WebPage.EXTENSIONS.push(file);
+    return this["native"]().injectJs(file);
+  };
+
+  /**
+   * Returns the native phantomjs webpage object
+   * @return {*}
+   */
+  WebPage.prototype["native"] = function () {
+    if (this.closed) {
+      throw new Poltergeist.NoSuchWindowError;
+    }
+
+    return this._native;
+  };
+
+  /**
+   * Returns the current page window name
+   * @return {*}
+   */
+  WebPage.prototype.windowName = function () {
+    return this["native"]().windowName;
+  };
+
+  /**
+   * Returns the keyCode of a given key as set in the phantomjs values
+   * @param name
+   * @return {number}
+   */
+  WebPage.prototype.keyCode = function (name) {
+    return this["native"]().event.key[name];
+  };
+
+  /**
+   * Waits for the page to reach a certain state
+   * @param state
+   * @param callback
+   * @return {*}
+   */
+  WebPage.prototype.waitState = function (state, callback) {
+    var self = this;
+    if (this.state === state) {
+      return callback.call();
+    } else {
+      return setTimeout((function () {
+        return self.waitState(state, callback);
+      }), 100);
+    }
+  };
+
+  /**
+   * Sets the browser header related to basic authentication protocol
+   * @param user
+   * @param password
+   * @return {boolean}
+   */
+  WebPage.prototype.setHttpAuth = function (user, password) {
+    var allHeaders = this.getCustomHeaders();
+
+    if (user === false || password === false) {
+      if (allHeaders.hasOwnProperty("Authorization")) {
+        delete allHeaders["Authorization"];
+      }
+      this.setCustomHeaders(allHeaders);
+      return true;
+    }
+
+    var userName = user || "";
+    var userPassword = password || "";
+
+    allHeaders["Authorization"] = "Basic " + btoa(userName + ":" + userPassword);
+    this.setCustomHeaders(allHeaders);
+    return true;
+  };
+
+  /**
+   * Returns all the network traffic associated to the rendering of this page
+   * @return {{}}
+   */
+  WebPage.prototype.networkTraffic = function () {
+    return this._networkTraffic;
+  };
+
+  /**
+   * Clears all the recorded network traffic related to the current page
+   * @return {{}}
+   */
+  WebPage.prototype.clearNetworkTraffic = function () {
+    return this._networkTraffic = {};
+  };
+
+  /**
+   * Returns the blocked urls that the page will not load
+   * @return {Array}
+   */
+  WebPage.prototype.blockedUrls = function () {
+    return this._blockedUrls;
+  };
+
+  /**
+   * Clean all the urls that should not be loaded
+   * @return {Array}
+   */
+  WebPage.prototype.clearBlockedUrls = function () {
+    return this._blockedUrls = [];
+  };
+
+  /**
+   * This property stores the content of the web page's currently active frame
+   * (which may or may not be the main frame), enclosed in an HTML/XML element.
+   * @return {string}
+   */
+  WebPage.prototype.content = function () {
+    return this["native"]().frameContent;
+  };
+
+  /**
+   * Returns the current active frame title
+   * @return {string}
+   */
+  WebPage.prototype.title = function () {
+    return this["native"]().frameTitle;
+  };
+
+  /**
+   * Returns if possible the frame url of the frame given by name
+   * @param frameName
+   * @return {string}
+   */
+  WebPage.prototype.frameUrl = function (frameName) {
+    var query;
+
+    query = function (frameName) {
+      var iframeReference;
+      if ((iframeReference = document.querySelector("iframe[name='" + frameName + "']")) != null) {
+        return iframeReference.src;
+      }
+      return void 0;
+    };
+
+    return this.evaluate(query, frameName);
+  };
+
+  /**
+   * Remove the errors caught on the page
+   * @return {Array}
+   */
+  WebPage.prototype.clearErrors = function () {
+    return this.errors = [];
+  };
+
+  /**
+   * Returns the response headers associated to this page
+   * @return {{}}
+   */
+  WebPage.prototype.responseHeaders = function () {
+    var headers;
+    headers = {};
+    this._responseHeaders.forEach(function (item) {
+      return headers[item.name] = item.value;
+    });
+    return headers;
+  };
+
+  /**
+   * Get Cookies visible to the current URL (though, for setting, use of page.addCookie is preferred).
+   * This array will be pre-populated by any existing Cookie data visible to this URL that is stored in the CookieJar, if any.
+   * @return {*}
+   */
+  WebPage.prototype.cookies = function () {
+    return this["native"]().cookies;
+  };
+
+  /**
+   * Delete any Cookies visible to the current URL with a 'name' property matching cookieName.
+   * Returns true if successfully deleted, otherwise false.
+   * @param name
+   * @return {*}
+   */
+  WebPage.prototype.deleteCookie = function (name) {
+    return this["native"]().deleteCookie(name);
+  };
+
+  /**
+   * This property gets the size of the viewport for the layout process.
+   * @return {*}
+   */
+  WebPage.prototype.viewportSize = function () {
+    return this["native"]().viewportSize;
+  };
+
+  /**
+   * This property sets the size of the viewport for the layout process.
+   * @param size
+   * @return {*}
+   */
+  WebPage.prototype.setViewportSize = function (size) {
+    return this["native"]().viewportSize = size;
+  };
+
+  /**
+   * This property specifies the scaling factor for the page.render and page.renderBase64 functions.
+   * @param zoomFactor
+   * @return {*}
+   */
+  WebPage.prototype.setZoomFactor = function (zoomFactor) {
+    return this["native"]().zoomFactor = zoomFactor;
+  };
+
+  /**
+   * This property defines the size of the web page when rendered as a PDF.
+   * See: http://phantomjs.org/api/webpage/property/paper-size.html
+   * @param size
+   * @return {*}
+   */
+  WebPage.prototype.setPaperSize = function (size) {
+    return this["native"]().paperSize = size;
+  };
+
+  /**
+   * This property gets the scroll position of the web page.
+   * @return {*}
+   */
+  WebPage.prototype.scrollPosition = function () {
+    return this["native"]().scrollPosition;
+  };
+
+  /**
+   * This property defines the scroll position of the web page.
+   * @param pos
+   * @return {*}
+   */
+  WebPage.prototype.setScrollPosition = function (pos) {
+    return this["native"]().scrollPosition = pos;
+  };
+
+
+  /**
+   * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
+   * If no clipping rectangle is set, page.render will process the entire web page.
+   * @return {*}
+   */
+  WebPage.prototype.clipRect = function () {
+    return this["native"]().clipRect;
+  };
+
+  /**
+   * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
+   * If no clipping rectangle is set, page.render will process the entire web page.
+   * @param rect
+   * @return {*}
+   */
+  WebPage.prototype.setClipRect = function (rect) {
+    return this["native"]().clipRect = rect;
+  };
+
+  /**
+   * Returns the size of an element given by a selector and its position relative to the viewport.
+   * @param selector
+   * @return {Object}
+   */
+  WebPage.prototype.elementBounds = function (selector) {
+    return this["native"]().evaluate(function (selector) {
+      return document.querySelector(selector).getBoundingClientRect();
+    }, selector);
+  };
+
+  /**
+   * Defines the user agent sent to server when the web page requests resources.
+   * @param userAgent
+   * @return {*}
+   */
+  WebPage.prototype.setUserAgent = function (userAgent) {
+    return this["native"]().settings.userAgent = userAgent;
+  };
+
+  /**
+   * Returns the additional HTTP request headers that will be sent to the server for EVERY request.
+   * @return {{}}
+   */
+  WebPage.prototype.getCustomHeaders = function () {
+    return this["native"]().customHeaders;
+  };
+
+  /**
+   * Gets the additional HTTP request headers that will be sent to the server for EVERY request.
+   * @param headers
+   * @return {*}
+   */
+  WebPage.prototype.setCustomHeaders = function (headers) {
+    return this["native"]().customHeaders = headers;
+  };
+
+  /**
+   * Adds a one time only request header, after being used it will be deleted
+   * @param header
+   * @return {Array}
+   */
+  WebPage.prototype.addTempHeader = function (header) {
+    var name, value, tempHeaderResult;
+    tempHeaderResult = [];
+    for (name in header) {
+      if (header.hasOwnProperty(name)) {
+        value = header[name];
+        tempHeaderResult.push(this._tempHeaders[name] = value);
+      }
+    }
+    return tempHeaderResult;
+  };
+
+  /**
+   * Remove the temporary headers we have set via addTempHeader
+   * @return {*}
+   */
+  WebPage.prototype.removeTempHeaders = function () {
+    var allHeaders, name, value, tempHeadersRef;
+    allHeaders = this.getCustomHeaders();
+    tempHeadersRef = this._tempHeaders;
+    for (name in tempHeadersRef) {
+      if (tempHeadersRef.hasOwnProperty(name)) {
+        value = tempHeadersRef[name];
+        delete allHeaders[name];
+      }
+    }
+
+    return this.setCustomHeaders(allHeaders);
+  };
+
+  /**
+   * If possible switch to the frame given by name
+   * @param name
+   * @return {boolean}
+   */
+  WebPage.prototype.pushFrame = function (name) {
+    if (this["native"]().switchToFrame(name)) {
+      this.frames.push(name);
+      return true;
+    }
+    return false;
+  };
+
+  /**
+   * Switch to parent frame, use with caution:
+   * popFrame assumes you are in frame, pop frame not being in a frame
+   * leaves unexpected behaviour
+   * @return {*}
+   */
+  WebPage.prototype.popFrame = function () {
+    //TODO: add some error control here, some way to check we are in a frame or not
+    this.frames.pop();
+    return this["native"]().switchToParentFrame();
+  };
+
+  /**
+   * Returns the webpage dimensions
+   * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
+   */
+  WebPage.prototype.dimensions = function () {
+    var scroll, viewport;
+    scroll = this.scrollPosition();
+    viewport = this.viewportSize();
+    return {
+      top: scroll.top,
+      bottom: scroll.top + viewport.height,
+      left: scroll.left,
+      right: scroll.left + viewport.width,
+      viewport: viewport,
+      document: this.documentSize()
+    };
+  };
+
+  /**
+   * Returns webpage dimensions that are valid
+   * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
+   */
+  WebPage.prototype.validatedDimensions = function () {
+    var dimensions, documentDimensions;
+
+    dimensions = this.dimensions();
+    documentDimensions = dimensions.document;
+
+    if (dimensions.right > documentDimensions.width) {
+      dimensions.left = Math.max(0, dimensions.left - (dimensions.right - documentDimensions.width));
+      dimensions.right = documentDimensions.width;
+    }
+
+    if (dimensions.bottom > documentDimensions.height) {
+      dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - documentDimensions.height));
+      dimensions.bottom = documentDimensions.height;
+    }
+
+    this.setScrollPosition({
+      left: dimensions.left,
+      top: dimensions.top
+    });
+
+    return dimensions;
+  };
+
+  /**
+   * Returns a Poltergeist.Node given by an id
+   * @param id
+   * @return {Poltergeist.Node}
+   */
+  WebPage.prototype.get = function (id) {
+    return new Poltergeist.Node(this, id);
+  };
+
+  /**
+   * Executes a phantomjs mouse event, for more info check: http://phantomjs.org/api/webpage/method/send-event.html
+   * @param name
+   * @param x
+   * @param y
+   * @param button
+   * @return {*}
+   */
+  WebPage.prototype.mouseEvent = function (name, x, y, button) {
+    if (button == null) {
+      button = 'left';
+    }
+    this.sendEvent('mousemove', x, y);
+    return this.sendEvent(name, x, y, button);
+  };
+
+  /**
+   * Evaluates a javascript and returns the evaluation of such script
+   * @return {*}
+   */
+  WebPage.prototype.evaluate = function () {
+    var args, fn;
+    fn = arguments[0];
+    args = [];
+
+    if (2 <= arguments.length) {
+      args = __slice.call(arguments, 1);
+    }
+
+    this.injectAgent();
+    return JSON.parse(this.sanitize(this["native"]().evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }")));
+  };
+
+  /**
+   * Does some string sanitation prior parsing
+   * @param potentialString
+   * @return {*}
+   */
+  WebPage.prototype.sanitize = function (potentialString) {
+    if (typeof potentialString === "string") {
+      return potentialString.replace("\n", "\\n").replace("\r", "\\r");
+    }
+
+    return potentialString;
+  };
+
+  /**
+   * Executes a script into the current page scope
+   * @param script
+   * @return {*}
+   */
+  WebPage.prototype.executeScript = function (script) {
+    return this["native"]().evaluateJavaScript(script);
+  };
+
+  /**
+   * Executes a script via phantomjs evaluation
+   * @return {*}
+   */
+  WebPage.prototype.execute = function () {
+    var args, fn;
+
+    fn = arguments[0];
+    args = [];
+
+    if (2 <= arguments.length) {
+      args = __slice.call(arguments, 1);
+    }
+
+    return this["native"]().evaluate("function() { " + (this.stringifyCall(fn, args)) + " }");
+  };
+
+  /**
+   * Helper methods to do script evaluation and execution
+   * @param fn
+   * @param args
+   * @return {string}
+   */
+  WebPage.prototype.stringifyCall = function (fn, args) {
+    if (args.length === 0) {
+      return "(" + (fn.toString()) + ")()";
+    }
+
+    return "(" + (fn.toString()) + ").apply(this, JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))";
+  };
+
+  /**
+   * Binds callbacks to their respective Native implementations
+   * @param name
+   * @return {Function}
+   */
+  WebPage.prototype.bindCallback = function (name) {
+    var self;
+    self = this;
+
+    return this["native"]()[name] = function () {
+      var result;
+      if (self[name + 'Native'] != null) {
+        result = self[name + 'Native'].apply(self, arguments);
+      }
+      if (result !== false && (self[name] != null)) {
+        return self[name].apply(self, arguments);
+      }
+    };
+  };
+
+  /**
+   * Runs a command delegating to the PoltergeistAgent
+   * @param name
+   * @param args
+   * @return {*}
+   */
+  WebPage.prototype.runCommand = function (name, args) {
+    var method, result, selector;
+
+    result = this.evaluate(function (name, args) {
+      return window.__poltergeist.externalCall(name, args);
+    }, name, args);
+
+    if (result !== null) {
+      if (result.error != null) {
+        switch (result.error.message) {
+          case 'PoltergeistAgent.ObsoleteNode':
+            throw new Poltergeist.ObsoleteNode;
+            break;
+          case 'PoltergeistAgent.InvalidSelector':
+            method = args[0];
+            selector = args[1];
+            throw new Poltergeist.InvalidSelector(method, selector);
+            break;
+          default:
+            throw new Poltergeist.BrowserError(result.error.message, result.error.stack);
+        }
+      } else {
+        return result.value;
+      }
+    }
+  };
+
+  /**
+   * Tells if we can go back or not
+   * @return {boolean}
+   */
+  WebPage.prototype.canGoBack = function () {
+    return this["native"]().canGoBack;
+  };
+
+  /**
+   * Tells if we can go forward or not in the browser history
+   * @return {boolean}
+   */
+  WebPage.prototype.canGoForward = function () {
+    return this["native"]().canGoForward;
+  };
+
+  return WebPage;
+
+}).call(this);
diff --git a/vendor/jcalderonzumba/gastonjs/src/Cookie.php b/vendor/jcalderonzumba/gastonjs/src/Cookie.php
new file mode 100644
index 0000000..d59c0f6
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Cookie.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Zumba\GastonJS;
+
+/**
+ * Class Cookie
+ * @package Zumba\GastonJS
+ */
+class Cookie {
+  /** @var  array */
+  protected $attributes;
+
+  /**
+   * @param $attributes
+   */
+  public function __construct($attributes) {
+    $this->attributes = $attributes;
+  }
+
+  /**
+   * Returns the cookie name
+   * @return string
+   */
+  public function getName() {
+    return $this->attributes['name'];
+  }
+
+  /**
+   * Returns the cookie value
+   * @return string
+   */
+  public function getValue() {
+    return urldecode($this->attributes['value']);
+  }
+
+  /**
+   * Returns the cookie domain
+   * @return string
+   */
+  public function getDomain() {
+    return $this->attributes['domain'];
+  }
+
+  /**
+   * Returns the path were the cookie is valid
+   * @return string
+   */
+  public function getPath() {
+    return $this->attributes['path'];
+  }
+
+  /**
+   * Is a secure cookie?
+   * @return bool
+   */
+  public function isSecure() {
+    return isset($this->attributes['secure']);
+  }
+
+  /**
+   * Is http only cookie?
+   * @return bool
+   */
+  public function isHttpOnly() {
+    return isset($this->attributes['httponly']);
+  }
+
+  /**
+   * Returns cookie expiration time
+   * @return mixed
+   */
+  public function getExpirationTime() {
+    //TODO: return a \DateTime object
+    if (isset($this->attributes['expiry'])) {
+      return $this->attributes['expiry'];
+    }
+    return null;
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php
new file mode 100644
index 0000000..a1c4f4b
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/BrowserError.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+
+/**
+ * Class BrowserError
+ * @package Zumba\GastonJS\Exception
+ */
+class BrowserError extends ClientError {
+
+  /**
+   * @param array $response
+   */
+  public function __construct($response) {
+    parent::__construct($response);
+    $this->message = $this->message();
+  }
+
+  /**
+   * Gets the name of the browser error
+   * @return string
+   */
+  public function getName() {
+    return $this->response["error"]["name"];
+  }
+
+  /**
+   * @return JSErrorItem
+   */
+  public function javascriptError() {
+    //TODO: this need to be check, i don't know yet what comes in response
+    return new JSErrorItem($this->response["error"]["args"][0], $this->response["error"]["args"][1]);
+  }
+
+  /**
+   * Returns error message
+   * TODO: check how to proper implement if we have exceptions
+   * @return string
+   */
+  public function message() {
+    return "There was an error inside the PhantomJS portion of GastonJS.\nThis is probably a bug, so please report it:\n" . $this->javascriptError();
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php
new file mode 100644
index 0000000..06be87b
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/ClientError.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class ClientError
+ * @package Zumba\GastonJS\Exception
+ */
+class ClientError extends \Exception {
+
+  /** @var mixed */
+  protected $response;
+
+  /**
+   * @param mixed $response
+   */
+  public function __construct($response) {
+    $this->response = $response;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getResponse() {
+    return $this->response;
+  }
+
+  /**
+   * @param mixed $response
+   */
+  public function setResponse($response) {
+    $this->response = $response;
+  }
+
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php b/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php
new file mode 100644
index 0000000..f4af193
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/DeadClient.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+
+/**
+ * Class DeadClient
+ * @package Zumba\GastonJS\Exception
+ */
+class DeadClient extends \Exception {
+
+  /**
+   * @param string     $message
+   * @param int        $code
+   * @param \Exception $previous
+   */
+  public function __construct($message = "", $code = 0, \Exception $previous = null) {
+    $errorMsg = $message."\nPhantomjs browser server is not taking connections, most probably it has crashed\n";
+    parent::__construct($errorMsg, $code, $previous);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/FrameNotFound.php b/vendor/jcalderonzumba/gastonjs/src/Exception/FrameNotFound.php
new file mode 100644
index 0000000..56a6f91
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/FrameNotFound.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class FrameNotFound
+ * @package Zumba\GastonJS\Exception
+ */
+class FrameNotFound extends ClientError {
+
+  /**
+   * @return string
+   */
+  public function getName() {
+    //TODO: check stuff here
+    return current(reset($this->response["args"]));
+  }
+
+  /**
+   * @return string
+   */
+  public function message() {
+    //TODO: check the exception message stuff
+    return "The frame " . $this->getName() . " was not not found";
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php b/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php
new file mode 100644
index 0000000..44fad4b
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/InvalidSelector.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class InvalidSelector
+ * @package Zumba\GastonJS\Exception
+ */
+class InvalidSelector extends ClientError {
+  /**
+   * Gets the method of selection
+   * @return string
+   */
+  public function getMethod() {
+    return $this->response["error"]["args"][0];
+  }
+
+  /**
+   * Gets the selector related to the method
+   * @return string
+   */
+  public function getSelector() {
+    return $this->response["error"]["args"][1];
+  }
+
+  /**
+   * @return string
+   */
+  public function message() {
+    return "The browser raised a syntax error while trying to evaluate" . $this->getMethod() . " selector " . $this->getSelector();
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php b/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php
new file mode 100644
index 0000000..2fa205a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/JSErrorItem.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class JSErrorItem
+ * @package Zumba\GastonJS\Exception
+ */
+class JSErrorItem {
+  /** @var  mixed */
+  protected $message;
+  /** @var  mixed */
+  protected $stack;
+
+  /**
+   * @param $message
+   * @param $stack
+   */
+  public function __construct($message, $stack) {
+    $this->message = $message;
+    $this->stack = $stack;
+  }
+
+  /**
+   * String representation of the class
+   * @return string
+   */
+  public function __toString() {
+    return sprintf("%s\n%s", $this->message, $this->stack);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php
new file mode 100644
index 0000000..309adfb
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/JavascriptError.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+
+/**
+ * Class JavascriptError
+ * @package Zumba\GastonJS\Exception
+ */
+class JavascriptError extends ClientError {
+
+  /**
+   * @param array $response
+   */
+  public function __construct($response) {
+    parent::__construct($response);
+    $this->message = $this->message();
+  }
+
+  /**
+   * Get the javascript errors found during the use of the phantomjs
+   * @return array
+   */
+  public function javascriptErrors() {
+    $jsErrors = array();
+    $errors = $this->response["error"]["args"][0];
+    foreach ($errors as $error) {
+      $jsErrors[] = new JSErrorItem($error["message"], $error["stack"]);
+    }
+    return $jsErrors;
+  }
+
+  /**
+   * Returns the javascript errors found
+   * @return string
+   */
+  public function message() {
+    $error = "One or more errors were raised in the Javascript code on the page.
+            If you don't care about these errors, you can ignore them by
+            setting js_errors: false in your Poltergeist configuration (see documentation for details).";
+    //TODO: add javascript errors
+    $jsErrors = $this->javascriptErrors();
+    foreach($jsErrors as $jsError){
+      $error = "$error\n$jsError";
+    }
+    return $error;
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php b/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php
new file mode 100644
index 0000000..abd72d3
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/MouseEventFailed.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class MouseEventFailed
+ * @package Zumba\GastonJS\Exception
+ */
+class MouseEventFailed extends NodeError {
+
+  /**
+   * Gets the name of the event
+   * @return string
+   */
+  public function getName() {
+    return $this->response["args"][0];
+  }
+
+  /**
+   * Selector of the element to act with the mouse
+   * @return string
+   */
+  public function getSelector() {
+    return $this->response["args"][1];
+  }
+
+  /**
+   * Returns the position where the click was done
+   * @return array
+   */
+  public function getPosition() {
+    $position = array();
+    $position[0] = $this->response["args"][1]['x'];
+    $position[1] = $this->response["args"][2]['y'];
+    return $position;
+  }
+
+  /**
+   * @return string
+   */
+  public function message() {
+    $name = $this->getName();
+    $position = implode(",", $this->getPosition());
+    return "Firing a $name at co-ordinates [$position] failed. Poltergeist detected
+            another element with CSS selector '#{selector}' at this position.
+            It may be overlapping the element you are trying to interact with.
+            If you don't care about overlapping elements, try using node.trigger('$name').";
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php
new file mode 100644
index 0000000..45388d1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/NoSuchWindowError.php
@@ -0,0 +1,10 @@
+<?php
+namespace Zumba\GastonJS\Exception;
+
+
+/**
+ * Class NoSuchWindowError
+ * @package Zumba\GastonJS\Exception
+ */
+class NoSuchWindowError extends ClientError {
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/NodeError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/NodeError.php
new file mode 100644
index 0000000..4b399cf
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/NodeError.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class NodeError
+ * @package Zumba\GastonJS\Exception
+ */
+class NodeError extends ClientError {
+  protected $node;
+
+  /**
+   * @param mixed $node
+   * @param mixed $response
+   */
+  public function __construct($node, $response) {
+    $this->node = $node;
+    parent::__construct($response);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php b/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php
new file mode 100644
index 0000000..a0cdb17
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/ObsoleteNode.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class ObsoleteNode
+ * @package Zumba\GastonJS\Exception
+ */
+class ObsoleteNode extends ClientError {
+
+  /**
+   * @param array $response
+   */
+  public function __construct($response) {
+    parent::__construct($response);
+    $this->message = $this->message();
+  }
+
+  /**
+   * @return string
+   */
+  public function message() {
+    return "The element you are trying to interact with is either not part of the DOM, or is
+    not currently visible on the page (perhaps display: none is set).
+    It's possible the element has been replaced by another element and you meant to interact with
+    the new element. If so you need to do a new 'find' in order to get a reference to the
+    new element.";
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php
new file mode 100644
index 0000000..fd90eef
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/StatusFailError.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+
+/**
+ * Class StatusFailError
+ * @package Zumba\GastonJS\Exception
+ */
+class StatusFailError extends ClientError {
+  /**
+   * @return string
+   */
+  public function message() {
+    return "Request failed to reach server, check DNS and/or server status";
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/Exception/TimeoutError.php b/vendor/jcalderonzumba/gastonjs/src/Exception/TimeoutError.php
new file mode 100644
index 0000000..6366ffa
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/Exception/TimeoutError.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Zumba\GastonJS\Exception;
+
+/**
+ * Class TimeoutError
+ * @package Zumba\GastonJS\Exception
+ */
+class TimeoutError extends \Exception {
+
+  /**
+   * @param string $message
+   */
+  public function __construct($message) {
+    $errorMessage = "Timed out waiting for response to {$message}. It's possible that this happened
+            because something took a very long time(for example a page load was slow).
+            If so, setting the Poltergeist :timeout option to a higher value will help
+            (see the docs for details). If increasing the timeout does not help, this is
+            probably a bug in Poltergeist - please report it to the issue tracker.";
+    parent::__construct($errorMessage);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Request.php b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Request.php
new file mode 100644
index 0000000..e6f9893
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Request.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Zumba\GastonJS\NetworkTraffic;
+
+/**
+ * Class Request
+ * @package Zumba\GastonJS\NetworkTraffic
+ */
+class Request {
+  /** @var array */
+  protected $data;
+  /** @var array */
+  protected $responseParts;
+
+
+  /**
+   * @param array $data
+   * @param array $responseParts
+   */
+  public function __construct($data, $responseParts = null) {
+    $this->data = $data;
+    $this->responseParts = $this->createResponseParts($responseParts);
+  }
+
+  /**
+   * Creates an array of Response objects from a given response array
+   * @param $responseParts
+   * @return array
+   */
+  protected function createResponseParts($responseParts) {
+    if ($responseParts === null) {
+      return array();
+    }
+    $responses = array();
+    foreach ($responseParts as $responsePart) {
+      $responses[] = new Response($responsePart);
+    }
+    return $responses;
+  }
+
+  /**
+   * @return array
+   */
+  public function getResponseParts() {
+    return $this->responseParts;
+  }
+
+  /**
+   * @param array $responseParts
+   */
+  public function setResponseParts($responseParts) {
+    $this->responseParts = $responseParts;
+  }
+
+  /**
+   * Returns the url where the request is going to be made
+   * @return string
+   */
+  public function getUrl() {
+    //TODO: add isset maybe?
+    return $this->data['url'];
+  }
+
+  /**
+   * Returns the request method
+   * @return string
+   */
+  public function getMethod() {
+    return $this->data['method'];
+  }
+
+  /**
+   * Gets the request headers
+   * @return array
+   */
+  public function getHeaders() {
+    //TODO: Check if the data is actually an array, else make it array and see implications
+    return $this->data['headers'];
+  }
+
+  /**
+   * Returns if exists the request time
+   * @return \DateTime
+   */
+  public function getTime() {
+    if (isset($this->data['time'])) {
+      $requestTime = new \DateTime();
+      //TODO: fix the microseconds to miliseconds
+      $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
+      return $requestTime;
+    }
+    return null;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php
new file mode 100644
index 0000000..37edc42
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/src/NetworkTraffic/Response.php
@@ -0,0 +1,97 @@
+<?php
+namespace Zumba\GastonJS\NetworkTraffic;
+
+/**
+ * Class Response
+ * @package Zumba\GastonJS\NetworkTraffic
+ */
+class Response {
+  /** @var  array */
+  protected $data;
+
+  /**
+   * @param $data
+   */
+  public function __construct($data) {
+    $this->data = $data;
+  }
+
+  /**
+   * Gets Response url
+   * @return string
+   */
+  public function getUrl() {
+    return $this->data['url'];
+  }
+
+  /**
+   * Gets the response status code
+   * @return int
+   */
+  public function getStatus() {
+    return intval($this->data['status']);
+  }
+
+  /**
+   * Gets the status text of the response
+   * @return string
+   */
+  public function getStatusText() {
+    return $this->data['statusText'];
+  }
+
+  /**
+   * Gets the response headers
+   * @return array
+   */
+  public function getHeaders() {
+    return $this->data['headers'];
+  }
+
+  /**
+   * Get redirect url if response is a redirect
+   * @return string
+   */
+  public function getRedirectUrl() {
+    if (isset($this->data['redirectUrl']) && !empty($this->data['redirectUrl'])) {
+      return $this->data['redirectUrl'];
+    }
+    return null;
+  }
+
+  /**
+   * Returns the size of the response body
+   * @return int
+   */
+  public function getBodySize() {
+    if (isset($this->data['bodySize'])) {
+      return intval($this->data['bodySize']);
+    }
+    return 0;
+  }
+
+  /**
+   * Returns the content type of the response
+   * @return string
+   */
+  public function getContentType() {
+    if (isset($this->data['contentType'])) {
+      return $this->data['contentType'];
+    }
+    return null;
+  }
+
+  /**
+   * Returns if exists the response time
+   * @return \DateTime
+   */
+  public function getTime() {
+    if (isset($this->data['time'])) {
+      $requestTime = new \DateTime();
+      //TODO: fix the microseconds to miliseconds
+      $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
+      return $requestTime;
+    }
+    return null;
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserAuthenticationTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserAuthenticationTest.php
new file mode 100644
index 0000000..c962683
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserAuthenticationTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserAuthenticationTest
+ * @package Zumba\GastonJS\Tests\Server
+ */
+class BrowserAuthenticationTest extends BrowserCommandsTestCase {
+
+  public function testAuthenticationFails() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/basic-auth-required/");
+    $this->assertEquals(401, $this->browser->getStatusCode());
+    $this->assertContains("NOT_AUTHORIZED", $this->browser->getBody());
+  }
+
+  public function testAuthenticationSuccess() {
+    $this->browser->setHttpAuth("test", "test");
+    $this->visitUrl($this->getTestPageBaseUrl() . "/basic-auth-required/");
+    $this->assertEquals(200, $this->browser->getStatusCode());
+    $this->assertContains("AUTHORIZATION_OK", $this->browser->getBody());
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCommandsTestCase.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCommandsTestCase.php
new file mode 100644
index 0000000..f330af7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCommandsTestCase.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+use Zumba\GastonJS\Browser\Browser;
+
+/**
+ * Class BrowserCommandsTestCase
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserCommandsTestCase extends \PHPUnit_Framework_TestCase {
+
+  const LOCAL_SERVER_HOSTNAME = "127.0.0.1";
+  const LOCAL_SERVER_PORT = 6789;
+
+  /** @var  Browser */
+  protected $browser;
+  /** @var  string */
+  protected $testPageBaseUrl;
+
+  protected function setUp() {
+    $this->browser = new Browser("http://127.0.0.1:8510/");
+    $this->browser->reset();
+    $this->testPageBaseUrl = sprintf("http://%s:%d", BrowserCommandsTestCase::LOCAL_SERVER_HOSTNAME, BrowserCommandsTestCase::LOCAL_SERVER_PORT);
+  }
+
+  /**
+   * Helper to visit a specific url
+   * @param string $url
+   */
+  protected function visitUrl($url) {
+    $this->assertNotEmpty($url);
+    $cmdResponse = $this->browser->visit($url);
+    $this->assertTrue(is_array($cmdResponse), true);
+    $this->assertEquals("success", $cmdResponse["status"]);
+  }
+
+  /**
+   * @return string
+   */
+  public function getTestPageBaseUrl() {
+    return $this->testPageBaseUrl;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCookiesTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCookiesTest.php
new file mode 100644
index 0000000..9618713
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserCookiesTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserCookiesTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserCookiesTest extends BrowserCommandsTestCase {
+
+  public function testCookiesAreEmpty() {
+    $this->assertEmpty($this->browser->cookies());
+  }
+
+  public function testCookiesAreNotEmpty() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/testCookiesAreNotEmpty/");
+    $cookies = $this->browser->cookies();
+    $this->assertCount(2, $cookies);
+    foreach ($cookies as $cookie) {
+      $this->assertInstanceOf('Zumba\GastonJS\Cookie', $cookie);
+    }
+  }
+
+  public function testClearCookies() {
+    //First we visit the page with cookies
+    $this->testCookiesAreNotEmpty();
+    //Then we issue a cookie clear
+    $this->assertTrue($this->browser->clearCookies());
+    //Then if we ask again it should be empty
+    $this->assertEmpty($this->browser->cookies());
+    //Then if we ask the basic page it should be empty
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEmpty($this->browser->cookies());
+  }
+
+  public function testRemoveCookie() {
+    //First we visit the page with cookies
+    $this->testCookiesAreNotEmpty();
+    //Then we issue the cookie removal with something that does not exists
+    $this->assertTrue($this->browser->removeCookie("DOES_NOT_EXITS"));
+    $this->assertCount(2, $this->browser->cookies());
+    //Now we issue a cookie removal that exists
+    $this->assertTrue($this->browser->removeCookie("a_cookie"));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertCount(1, $this->browser->cookies());
+  }
+
+  public function testSetCookie() {
+    $cookie = array("name" => "mycookie", "value" => "myvalue", "path" => "/", "domain" => "127.0.0.1");
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEmpty($this->browser->cookies());
+    $this->assertTrue($this->browser->setCookie($cookie));
+    $this->browser->reload();
+    $this->assertArrayHasKey("mycookie", $this->browser->cookies());
+  }
+
+  public function testCookiesDisabled() {
+    $this->assertTrue($this->browser->cookiesEnabled(false));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/testCookiesAreNotEmpty/");
+    //Should be zero since we have disabled the cookies
+    $this->assertEmpty($this->browser->cookies());
+  }
+
+  public function testCookiesEnabled(){
+    $this->assertTrue($this->browser->cookiesEnabled(true));
+    $this->testCookiesAreNotEmpty();
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserElementAttributesTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserElementAttributesTest.php
new file mode 100644
index 0000000..504392d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserElementAttributesTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+
+/**
+ * Class BrowserElementAttributesTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserElementAttributesTest extends BrowserCommandsTestCase {
+
+  public function testAttributes() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $this->browser->find("xpath", '//*[@id="element_3"]');
+    $attributes = $this->browser->attributes(1, 0);
+    $this->assertCount(3, $attributes);
+    $this->assertArraySubset(array("class" => "element select medium", "id" => "element_3", "name" => "element_3"), $attributes);
+  }
+
+  public function testAttribute() {
+    $this->testAttributes();
+    $this->assertEquals("element select medium", $this->browser->attribute(1, 0, "class"));
+    $this->assertEquals("element_3", $this->browser->attribute(1, 0, "id"));
+    $this->assertEquals("element_3", $this->browser->attribute(1, 0, "name"));
+  }
+
+  public function testSetAttribute() {
+    $this->testAttributes();
+    $this->assertTrue($this->browser->setAttribute(1, 0, "class", "element select"));
+    $attributes = $this->browser->attributes(1, 0);
+    $this->assertCount(3, $attributes);
+    $this->assertArraySubset(array("class" => "element select", "id" => "element_3", "name" => "element_3"), $attributes);
+  }
+
+  public function testRemoveAttribute(){
+    $this->testAttributes();
+    $this->assertTrue($this->browser->removeAttribute(1, 0, "THIS_DOES_NOT_EXISTS"));
+    $this->assertTrue($this->browser->removeAttribute(1, 0, "class"));
+    $attributes = $this->browser->attributes(1, 0);
+    $this->assertCount(2, $attributes);
+    $this->assertArraySubset(array("id" => "element_3", "name" => "element_3"), $attributes);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserFileTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserFileTest.php
new file mode 100644
index 0000000..0530b57
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserFileTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserFileTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserFileTest extends BrowserCommandsTestCase {
+
+  public function testFileUpload() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $fileLocation = sprintf("%s/Server/www/web/test/standard_form/image_for_test.png", __DIR__);
+    $this->assertCount(2, $this->browser->find("xpath", '//*[@id="element_2"]'));
+    $this->assertTrue($this->browser->selectFile(1, 0, $fileLocation));
+    $this->assertCount(2, $this->browser->find("xpath", '//*[@id="form_1014473"]'));
+    $this->assertEquals("submit", $this->browser->trigger(1, 1, "submit"));
+    //STUFF works like that, lets give 2 seconds to the browser for the page to load
+    sleep(2);
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $expectedResponse = array("image_for_test.png" => array("file_name" => "image_for_test.png", "is_valid" => 1, "mime_type" => "image/png"));
+    $this->assertArraySubset($expectedResponse, $requestResponse["files"]);
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserHeadersTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserHeadersTest.php
new file mode 100644
index 0000000..0225cb1
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserHeadersTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserHeadersTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserHeadersTest extends BrowserCommandsTestCase {
+
+  public function testHeadersEmpty() {
+    $this->assertCount(0, $this->browser->getHeaders());
+  }
+
+  public function testHeadersNotEmpty() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $responseHeaders = $this->browser->responseHeaders();
+    $this->assertTrue(is_array($responseHeaders));
+    $this->assertCount(4, $responseHeaders);
+  }
+
+  public function testHeaderAddPermanent() {
+    $this->assertTrue($this->browser->addHeader(array("X-Permanent-Test" => "x_permanent_value"), true));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertEquals("x_permanent_value", $requestResponse["x-permanent-test"][0]);
+
+    //After reload, the header should still be there
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertEquals("x_permanent_value", $requestResponse["x-permanent-test"][0]);
+  }
+
+  public function testHeaderAddTemp() {
+    $this->assertTrue($this->browser->addHeader(array("X-Permanent-Test" => "x_permanent_value")));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertEquals("x_permanent_value", $requestResponse["x-permanent-test"][0]);
+
+    //After the visit the header should not be present in the request
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertArrayNotHasKey("x-permanent-test", $requestResponse);
+  }
+
+  public function testAddHeaders() {
+    $customHeaders = array("X-Header-One" => "one", "X-Header-Two" => "two");
+    $this->assertTrue($this->browser->addHeaders($customHeaders));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertArrayHasKey("x-header-one", $requestResponse);
+    $this->assertArrayHasKey("x-header-two", $requestResponse);
+
+    //now we will issue another header and the previous should be there too
+    $this->assertTrue($this->browser->addHeaders(array("X-Header-Three" => "three")));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertArrayHasKey("x-header-one", $requestResponse);
+    $this->assertArrayHasKey("x-header-two", $requestResponse);
+    $this->assertArrayHasKey("x-header-three", $requestResponse);
+  }
+
+
+  public function testSetHeaders() {
+    $customHeaders = array("X-Header-One" => "one", "X-Header-Two" => "two");
+    $this->assertTrue($this->browser->setHeaders($customHeaders));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertArrayHasKey("x-header-one", $requestResponse);
+    $this->assertArrayHasKey("x-header-two", $requestResponse);
+
+    //now we will issue another header and the previous should NOT be here
+    $this->assertTrue($this->browser->setHeaders(array("X-Header-Three" => "three")));
+    $this->visitUrl($this->getTestPageBaseUrl() . "/check-request-headers/");
+    $requestResponse = json_decode(strip_tags($this->browser->getBody()), true);
+    $this->assertArrayHasKey("x-header-three", $requestResponse);
+    $this->assertArrayNotHasKey("x-header-one", $requestResponse);
+    $this->assertArrayNotHasKey("x-header-two", $requestResponse);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNavigateTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNavigateTest.php
new file mode 100644
index 0000000..6b87354
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNavigateTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserNavigateTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserNavigateTest extends BrowserCommandsTestCase {
+
+  public function testBrowserVisit() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+  }
+
+  public function testBrowserCurrentUrl() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $currentUrl = $this->browser->currentUrl();
+    $this->assertEquals($this->getTestPageBaseUrl() . "/static/basic.html", $currentUrl);
+  }
+
+  public function testBrowserReload() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertTrue($this->browser->reload());
+    $currentUrl = $this->browser->currentUrl();
+    $this->assertEquals($this->getTestPageBaseUrl() . "/static/basic.html", $currentUrl);
+  }
+
+  public function testGoBack() {
+    //We have a clean slate so the first try should say no
+    $this->assertFalse($this->browser->goBack());
+    $this->testBrowserVisit();
+    //First visit still needs to be false
+    $this->assertFalse($this->browser->goBack());
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/auth_ok.html");
+    $this->assertTrue($this->browser->goBack());
+    $this->assertEquals($this->getTestPageBaseUrl() . "/static/basic.html", $this->browser->currentUrl());
+  }
+
+  public function testGoForward() {
+    //We have a clean slate so the first try should say no
+    $this->assertFalse($this->browser->goForward());
+    $this->testBrowserVisit();
+    //Still can not go forward
+    $this->assertFalse($this->browser->goForward());
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/auth_ok.html");
+    //Now we can go back and forward
+    $this->assertTrue($this->browser->goBack());
+    $this->assertTrue($this->browser->goForward());
+    $this->assertEquals($this->getTestPageBaseUrl() . "/static/auth_ok.html", $this->browser->currentUrl());
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNetworkTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNetworkTest.php
new file mode 100644
index 0000000..bea5757
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserNetworkTest.php
@@ -0,0 +1,27 @@
+<?php
+namespace Zumba\GastonJS\Tests;
+
+
+/**
+ * Class BrowserNetworkTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserNetworkTest extends BrowserCommandsTestCase {
+
+  public function testNetworkTraffic() {
+    $this->assertEmpty($this->browser->networkTraffic());
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $traffic = $this->browser->networkTraffic();
+    $this->assertCount(6, $traffic);
+    $this->assertInstanceOf("Zumba\\GastonJS\\NetworkTraffic\\Request", $traffic[0]);
+    $this->assertNotEmpty($traffic[0]->getResponseParts());
+    $this->assertInstanceOf("Zumba\\GastonJS\\NetworkTraffic\\Response", $traffic[0]->getResponseParts()[0]);
+  }
+
+  public function testClearNetworkTraffic(){
+    $this->testNetworkTraffic();
+    $this->assertTrue($this->browser->clearNetworkTraffic());
+    $this->assertEmpty($this->browser->networkTraffic());
+  }
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageContentTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageContentTest.php
new file mode 100644
index 0000000..4c669e3
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageContentTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+use Zumba\GastonJS\Exception\ObsoleteNode;
+
+/**
+ * Class BrowserPageContentTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserPageContentTest extends BrowserCommandsTestCase {
+
+  public function testAllText() {
+    try {
+      $this->browser->allText(1, 0);
+    } catch (ObsoleteNode $e) {
+    }
+
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->browser->find("xpath", '//*[@id="break"]');
+    $this->assertEquals("Foo Bar", trim($this->browser->allText(1, 0)));
+    $this->browser->find("xpath", '//*[@id="nav"]');
+    $this->assertEquals("Home", trim($this->browser->allText(1, 1)));
+    $this->browser->find("xpath", '/html/body');
+    $text = trim($this->browser->allText(1, 2));
+    //This could be done better, but for the moment is just like this..
+    $this->assertContains("Home", $text);
+    $this->assertContains("Link", $text);
+    $this->assertContains("Foo Bar", $text);
+    $this->assertContains("THIS SHOULD NOT BE SEEN", $text);
+  }
+
+  public function testVisibleText() {
+    $this->testAllText();
+    $this->assertEquals("Foo Bar", trim($this->browser->visibleText(1, 0)));
+    $this->assertEquals("Home", trim($this->browser->visibleText(1, 1)));
+    $text = trim($this->browser->visibleText(1, 2));
+    $this->assertContains("Home", $text);
+    $this->assertContains("Link", $text);
+    $this->assertContains("Foo Bar", $text);
+    $this->assertNotContains("THIS SHOULD NOT BE SEEN", $text);
+  }
+
+  public function testAllHtml() {
+    try {
+      $this->browser->allHtml(1, 0);
+    } catch (ObsoleteNode $e) {
+    }
+
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->browser->find("xpath", '/html/body/ul');
+    $innerHtml = trim($this->browser->allHtml(1, 0, "inner"));
+    $outerHtml = trim($this->browser->allHtml(1, 0, "outer"));
+    $html= '<li><a id="nav" href="/">Home</a></li>';
+    $this->assertXmlStringEqualsXmlString($html, $innerHtml);
+    $this->assertXmlStringEqualsXmlString("<ul>$html</ul>", $outerHtml);
+    $this->assertEmpty($this->browser->allHtml(1, 0, "not_valid"));
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageFindTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageFindTest.php
new file mode 100644
index 0000000..bd7b186
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageFindTest.php
@@ -0,0 +1,91 @@
+<?php
+namespace Zumba\GastonJS\Tests;
+
+use Zumba\GastonJS\Exception\BrowserError;
+use Zumba\GastonJS\Exception\InvalidSelector;
+use Zumba\GastonJS\Exception\ObsoleteNode;
+
+/**
+ * Class BrowserPageFindTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserPageFindTest extends BrowserCommandsTestCase {
+
+  public function testFindElementNoPage() {
+    $notFound = $this->browser->find("xpath", '//*[@id="form_1014473"]');
+    $this->assertEquals(0, $notFound["page_id"]);
+  }
+
+  public function testFindInvalidSelector() {
+    $selector = "xpath";
+    $invalidSelection = '//*INVALID_SELECTOR[@id="form_1014473"]';
+    try {
+      $this->browser->find($selector, $invalidSelection);
+    } catch (InvalidSelector $e) {
+      $this->assertEquals($selector, $e->getMethod());
+      $this->assertEquals($invalidSelection, $e->getSelector());
+    }
+  }
+
+  public function testFindElementPage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $element = $this->browser->find("xpath", '//*[@id="form_1014473"]');
+    $this->assertEquals(1, $element["page_id"]);
+    $this->assertEquals(0, $element["ids"][0]);
+    $cssElement = $this->browser->find("css", "#form_1014473 > div");
+    $this->assertEquals(1, $cssElement["page_id"]);
+    $this->assertEquals(1, $cssElement["ids"][0]);
+  }
+
+  public function testFindWithinElementNoPage() {
+    try {
+      $this->browser->findWithin(1, 0, "xpath", '//*[@id="li_1"]');
+    } catch (\Exception $e) {
+      $this->assertInstanceOf("Zumba\\GastonJS\\Exception\\ObsoleteNode", $e);
+    }
+  }
+
+  public function testFindWithinElementPage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $this->browser->find("css", "#form_1014473");
+    $withinElement = $this->browser->findWithin(1, 0, "css", "div");
+    $this->assertCount(4, $withinElement);
+  }
+
+  public function testGetParents() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $this->browser->find("xpath", '//*[@id="form_1014473"]');
+    $this->assertArraySubset(array(1, 2, 3), $this->browser->getParents(1, 0));
+  }
+
+  public function testTagName() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    $this->browser->find("xpath", '//*[@id="form_1014473"]');
+    $this->assertEquals("form", $this->browser->tagName(1, 0));
+  }
+
+  public function testEquals() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/standard_form/form.html");
+    try {
+      $this->browser->equals(1, 0, 1);
+    } catch (ObsoleteNode $e) {
+    }
+    //TODO: equals method seems to be broken or i do not know how to use it
+  }
+
+  public function testIsVisible() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->browser->find("xpath", '//*[@id="break"]');
+    $this->assertTrue($this->browser->isVisible(1, 0));
+    $this->browser->find("xpath", '/html/body/p[1]');
+    $this->assertFalse($this->browser->isVisible(1, 1));
+  }
+
+  public function testIsDisabled() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->browser->find("xpath", '//*[@id="disabled_check"]');
+    $this->browser->find("xpath", '//*[@id="enabled_check"]');
+    $this->assertTrue($this->browser->isDisabled(1, 0));
+    $this->assertFalse($this->browser->isDisabled(1, 1));
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageTest.php
new file mode 100644
index 0000000..df1cf0b
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserPageTest.php
@@ -0,0 +1,82 @@
+<?php
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserPageTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserPageTest extends BrowserCommandsTestCase {
+
+  public function testGetStatusCodeNoPage() {
+    try {
+      $this->browser->getStatusCode();
+    } catch (\Exception $e) {
+      $this->assertInstanceOf("Zumba\\GastonJS\\Exception\\StatusFailError", $e);
+    }
+  }
+
+  public function testGetStatusCodePage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEquals(200, $this->browser->getStatusCode());
+    $this->visitUrl($this->getTestPageBaseUrl() . "/this_does_not_exists");
+    $this->assertEquals(404, $this->browser->getStatusCode());
+  }
+
+  public function testGetBodyNoPage() {
+    $expectedBody = "<html><head></head><body></body></html>";
+    $this->assertEquals($expectedBody, $this->browser->getBody());
+  }
+
+  public function testGetBodyPage() {
+    $htmlFile = sprintf("%s/Server/www/web/static/basic.html", realpath(__DIR__));
+    $expectedDom = new \DOMDocument();
+    $expectedDom->loadHTMLFile($htmlFile);
+    $expectedDom->preserveWhiteSpace = false;
+    $expectedDom->formatOutput = true;
+
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $pageDom = new \DOMDocument();
+    $pageDom->loadHTML($this->browser->getBody());
+    $pageDom->preserveWhiteSpace = false;
+    $pageDom->formatOutput = true;
+
+    $this->assertXmlStringEqualsXmlString($pageDom->saveXML(), $expectedDom->saveXML());
+  }
+
+  public function testGetSourceNoPage() {
+    $this->assertNull($this->browser->getSource());
+  }
+
+  public function testGetSourcePage() {
+    $htmlFile = sprintf("%s/Server/www/web/static/basic.html", realpath(__DIR__));
+    $expectedDom = new \DOMDocument();
+    $expectedDom->loadHTMLFile($htmlFile);
+    $expectedDom->preserveWhiteSpace = false;
+    $expectedDom->formatOutput = true;
+
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $pageDom = new \DOMDocument();
+    $pageDom->loadHTML($this->browser->getSource());
+    $pageDom->preserveWhiteSpace = false;
+    $pageDom->formatOutput = true;
+
+    $this->assertXmlStringEqualsXmlString($pageDom->saveXML(), $expectedDom->saveXML());
+  }
+
+  public function testGetTitle() {
+    $this->assertEmpty($this->browser->getTitle());
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEquals("Test", $this->browser->getTitle());
+  }
+
+  public function testReset() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertTrue($this->browser->reset());
+    $this->testGetStatusCodeNoPage();
+    $this->testGetBodyNoPage();
+    $this->testGetSourceNoPage();
+    //TODO: increase reset tests by testing for example cookies
+  }
+
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserRenderTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserRenderTest.php
new file mode 100644
index 0000000..704c3a9
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserRenderTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserRenderTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserRenderTest extends BrowserCommandsTestCase {
+
+  public function testRenderBase64() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    //Check we get a string
+    $stringData = $this->browser->renderBase64("png");
+    $this->assertTrue(is_string($stringData));
+    $binaryData = base64_decode($stringData, true);
+    //now we check that the binary data is actually PNG
+    $fileInfo = new \finfo(FILEINFO_MIME);
+    $this->assertNotFalse($binaryData);
+    $this->assertNotFalse(strstr($fileInfo->buffer($binaryData), "image/png"));
+  }
+
+  public function testRenderFile() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $tempFile = sprintf("%s/%d.png", sys_get_temp_dir(), time());
+    $this->assertTrue($this->browser->render($tempFile));
+    $this->assertFileExists($tempFile);
+  }
+
+  //TODO: Test properly the selection stuff and the paper size
+
+
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserScriptTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserScriptTest.php
new file mode 100644
index 0000000..f3e7bce
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserScriptTest.php
@@ -0,0 +1,52 @@
+<?php
+namespace Zumba\GastonJS\Tests;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Message\Response;
+
+/**
+ * Class BrowserScriptTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserScriptTest extends BrowserCommandsTestCase {
+
+  public function testScriptEvaluate() {
+    $webClient = new Client();
+    /** @var $response Response */
+    $response = $webClient->get($this->getTestPageBaseUrl() . "/static/fibonacci.js");
+    $script = $response->getBody()->getContents();
+    //Fibonacci(10) = 55;
+    $this->assertEquals(55, $this->browser->evaluate($script));
+  }
+
+  protected function doFormSubmit() {
+    //We have changed the form so lets get the element and see such changes
+    $formElement = $this->browser->find("xpath", '//*[@id="form_1014473"]');
+    $this->assertEquals("submit", $this->browser->trigger($formElement["page_id"], $formElement["ids"][0], "submit"));
+    //STUFF works like that, lets give time for the browser and server to load.
+    sleep(2);
+    $expectedUrl = "http://127.0.0.1:6789/check-post-request/";
+    $this->assertEquals($expectedUrl, $this->browser->currentUrl());
+    $formResponse = json_decode(strip_tags($this->browser->getSource()), true);
+    $this->assertTrue(is_array($formResponse));
+    $this->assertEquals("THIS_IS_SPARTA", $formResponse["post"]["element_1"]);
+    $this->assertEquals("1", $formResponse["post"]["element_3"]);
+  }
+
+  public function testScriptExecute() {
+    $webClient = new Client();
+    /** @var $response Response */
+    $response = $webClient->get($this->getTestPageBaseUrl() . "/test/script_execute/execute.js");
+    $script = $response->getBody()->getContents();
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/script_execute/form.html");
+    $this->assertTrue($this->browser->execute($script));
+    $this->doFormSubmit();
+  }
+
+  public function testScriptExtensions() {
+    $viewJS = sprintf("%s/Server/www/web/test/script_extensions/extension.js", __DIR__);
+    $this->visitUrl($this->getTestPageBaseUrl() . "/test/script_extensions/form.html");
+    $this->assertTrue($this->browser->extensions(array($viewJS)));
+    $this->doFormSubmit();
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserWindowTest.php b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserWindowTest.php
new file mode 100644
index 0000000..f430c6d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/BrowserWindowTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Zumba\GastonJS\Tests;
+
+/**
+ * Class BrowserWindowTest
+ * @package Zumba\GastonJS\Tests
+ */
+class BrowserWindowTest extends BrowserCommandsTestCase {
+
+  public function testWindowHandleNoPage() {
+    $this->assertEquals(0, $this->browser->windowHandle());
+  }
+
+  public function testWindowHandlePage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEquals(0, $this->browser->windowHandle());
+  }
+
+  public function testWindowNameNoPage() {
+    $this->assertEmpty($this->browser->windowName());
+  }
+
+  public function testWindowNamePage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEquals("BASIC_WINDOW", $this->browser->windowName());
+  }
+
+  public function testCloseWindow() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->assertEquals(0, $this->browser->windowHandle());
+    $this->browser->closeWindow("0");
+    $this->assertNull($this->browser->windowHandle());
+  }
+
+  public function testWindowHandlesNoPage() {
+    $handles = $this->browser->windowHandles();
+    $this->assertCount(1, $handles);
+    $this->assertEquals($handles[0], "0");
+  }
+
+  public function testOpenNewWindow() {
+    $this->assertTrue($this->browser->openNewWindow());
+    $this->assertEquals("about:blank", $this->browser->currentUrl());
+  }
+
+  public function testWindowHandlesPage() {
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/basic.html");
+    $this->browser->openNewWindow();
+    $this->visitUrl($this->getTestPageBaseUrl() . "/static/auth_ok.html");
+    $this->assertCount(2, $this->browser->windowHandles());
+  }
+
+  public function testSwitchToWindow() {
+    $this->testWindowHandlesPage();
+    $this->assertEquals(0, $this->browser->windowHandle());
+    $this->assertTrue($this->browser->switchToWindow("1"));
+    $this->assertEquals(1, $this->browser->windowHandle());
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/LocalWebServer.php b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/LocalWebServer.php
new file mode 100644
index 0000000..f818302
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/LocalWebServer.php
@@ -0,0 +1,92 @@
+<?php
+namespace Zumba\GastonJS\Tests\Server;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Class LocalWebServer
+ * @package Zumba\GastonJS\Tests\Server
+ */
+class LocalWebServer {
+  /** @var  Process */
+  protected $process;
+  /** @var LocalWebServer */
+  private static $instance = null;
+
+  /**
+   * Private constructor for an local web server instance
+   * @param $serverOptions
+   * @param $workingDir
+   */
+  private function __construct($serverOptions, $workingDir) {
+    echo "Creating local server with $serverOptions and working dir $workingDir\n";
+    $this->waitForServerToStop();
+    $this->process = new Process("php -S 127.0.0.1:6789 $serverOptions", $workingDir);
+    $this->process->start();
+    $this->waitForServerStart();
+  }
+
+  /**
+   * When collecting make the stuff die
+   */
+  public function __destruct() {
+    echo "Stopping the local server...\n";
+    $this->process->stop();
+  }
+
+  /**
+   * If the server is up wait for it to stop
+   */
+  protected function waitForServerToStop() {
+    echo "Waiting for local server to die...\n";
+    $serverUp = true;
+    while ($serverUp) {
+      $sock = @fsockopen("127.0.0.1", 6789, $errno, $errstr, 5);
+      if (is_resource($sock)) {
+        fclose($sock);
+        $serverUp = true;
+        echo "Server still listening to connections waiting..\n";
+        sleep(1);
+      } else {
+        echo "Server is not listening connection $errno $errstr, getting out..\n";
+        $serverUp = false;
+      }
+    }
+  }
+
+  protected function waitForServerStart() {
+    $notReady = true;
+    echo "Waiting for local server to startup...\n";
+    while ($notReady) {
+      $sock = @fsockopen("127.0.0.1", 6789, $errno, $errstr, 5);
+      if (is_resource($sock)) {
+        fclose($sock);
+        $notReady = false;
+      } else {
+        echo "Not ready yet $errno, $errstr\n";
+        sleep(1);
+      }
+    }
+    echo "Local server ready to start testing...\n";
+  }
+
+  /**
+   * Creates or returns the local server instance
+   * @param        $serverOptions
+   * @param string $workingDir
+   * @return LocalWebServer
+   */
+  public static function getInstance($serverOptions, $workingDir = __DIR__) {
+    if (null === self::$instance) {
+      self::$instance = new self($serverOptions, $workingDir);
+    }
+    return self::$instance;
+  }
+
+  /**
+   * @return Process
+   */
+  public function getProcess() {
+    return $this->process;
+  }
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/index.php b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/index.php
new file mode 100644
index 0000000..b8429cd
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/index.php
@@ -0,0 +1,75 @@
+<?php
+require_once __DIR__ . '/../../../../../vendor/autoload.php';
+
+use Silex\Application;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+$filename = __DIR__ . preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']);
+if (php_sapi_name() === 'cli-server' && is_file($filename)) {
+  return false;
+}
+
+$app = new Application();
+
+$app->get("/testCookiesAreNotEmpty/", function (Request $request) {
+  $testResponse = new Response();
+  $htmlContents = file_get_contents(sprintf("%s/static/basic.html", __DIR__));
+  $testResponse->setContent($htmlContents);
+  $testResponse->setStatusCode(200);
+  $testResponse->headers->setCookie(new Cookie("a_cookie", "a_has_value"));
+  $testResponse->headers->setCookie(new Cookie("b_cookie", "b_has_value"));
+  return $testResponse;
+});
+
+$app->get("/basic-auth-required/", function (Request $request) {
+  $response = new Response();
+  if (!isset($_SERVER["PHP_AUTH_USER"]) || !isset($_SERVER["PHP_AUTH_PW"])) {
+    $response->headers->set("WWW-Authenticate", 'Basic realm="TEST_REALM"');
+    $response->setStatusCode(401);
+    $response->setContent("NOT_AUTHORIZED");
+    return $response;
+  }
+
+  if ($_SERVER["PHP_AUTH_USER"] != "test" || $_SERVER["PHP_AUTH_PW"] != "test") {
+    $response->setStatusCode(401);
+    $response->setContent("NOT_AUTHORIZED");
+    return $response;
+  }
+  $htmlContents = file_get_contents(sprintf("%s/static/auth_ok.html", __DIR__));
+  $response->setContent($htmlContents);
+  $response->setStatusCode(200);
+  return $response;
+});
+
+//Route used for header related test
+$app->get("/check-request-headers/", function (Request $request) {
+  $response = new Response();
+  $response->headers->set("Content-Type", "application/json");
+  $response->setStatusCode(200);
+  $jsonResponse = json_encode($request->headers->all());
+  $response->setContent($jsonResponse);
+  return $response;
+});
+
+$app->post("/check-post-request/", function (Request $request) {
+  $response = new Response();
+  $response->headers->set("Content-Type", "application/json");
+  $response->setStatusCode(200);
+  $jsonResponse["post"] = $request->request->all();
+  $jsonResponse["get"] = $request->query->all();
+  if (count($request->files->all()) !== 0) {
+    /** @var $file \Symfony\Component\HttpFoundation\File\UploadedFile */
+    foreach ($request->files->all() as $file) {
+      if ($file instanceof UploadedFile) {
+        $jsonResponse["files"][$file->getClientOriginalName()] = array("file_name" => $file->getClientOriginalName(), "is_valid" => $file->isValid(), "mime_type" => $file->getMimeType());
+      }
+    }
+  }
+  $jsonResponse = json_encode($jsonResponse);
+  $response->setContent($jsonResponse);
+  return $response;
+});
+$app->run();
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/auth_ok.html b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/auth_ok.html
new file mode 100755
index 0000000..025b08c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/auth_ok.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>AUTH_OK</title>
+</head>
+
+<body>
+AUTHORIZATION_OK
+</body>
+</html>
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/basic.html b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/basic.html
new file mode 100755
index 0000000..1f4bef8
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/basic.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+  <title>Test</title>
+  <script type="application/javascript">
+    window.name = "BASIC_WINDOW";
+  </script>
+</head>
+<body>
+<ul>
+  <li>
+    <a id="nav" href="/">Home</a>
+  </li>
+</ul>
+<a href="/">Link</a>
+<p style="display: none;">THIS SHOULD NOT BE SEEN</p>
+<p id="break">Foo Bar</p>
+<form enctype="multipart/form-data" action="#" method="post">
+  <input type="text" name="disabled_check" id="disabled_check" disabled="disabled"/>
+  <input type="checkbox" name="enabled_check" id="enabled_check" value="111"/>
+</form>
+</body>
+</html>
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/fibonacci.js b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/fibonacci.js
new file mode 100755
index 0000000..eae39fc
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/static/fibonacci.js
@@ -0,0 +1,12 @@
+(function (fibonnaciNumber) {
+  var looping = function (n) {
+    var a = 0, b = 1, f = 1;
+    for (var i = 2; i <= n; i++) {
+      f = a + b;
+      a = b;
+      b = f;
+    }
+    return f;
+  };
+  return looping(fibonnaciNumber);
+})(10);
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/blank.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/blank.gif
new file mode 100755
index 0000000..75b945d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/blank.gif
@@ -0,0 +1 @@
+GIF89a         !   ,       T ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/bottom.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/bottom.png
new file mode 100755
index 0000000..7f46c80
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/bottom.png
@@ -0,0 +1,6 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  AIDATxQO02?WTH䳖e>M<'|ϽdS)%MҞ˖n\R  <GY,5ۖùv~_.~rW."   -m]f%
+@A8*y, 0)  pZ(2Ĥ|0Ak   _;`Tw5   畁~>J9*6xPc	P  /8зç=zX	  @eP hѣAAs?  b{s   ~   |
+0 %X    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/execute.js b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/execute.js
new file mode 100644
index 0000000..c091247
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/execute.js
@@ -0,0 +1,4 @@
+(function () {
+  document.getElementById("element_1").value = "THIS_IS_SPARTA";
+  document.getElementById("element_3").selectedIndex = 1;
+})();
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/form.html b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/form.html
new file mode 100755
index 0000000..00ab833
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/form.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>TEST FORM</title>
+  <link rel="stylesheet" type="text/css" href="view.css" media="all">
+  <script type="text/javascript" src="view.js"></script>
+
+</head>
+<body id="main_body">
+
+<img id="top" src="top.png" alt="">
+
+<div id="form_container">
+
+  <h1><a>TEST FORM</a></h1>
+
+  <form id="form_1014473" class="appnitro" enctype="multipart/form-data" method="post" action="/check-post-request/" onsubmit="return this.submit();">
+    <div class="form_description">
+      <h2>TEST FORM</h2>
+
+      <p>Test form for testing purposes</p>
+    </div>
+    <ul>
+
+      <li id="li_1">
+        <label class="description" for="element_1">TYPE YOUR TEXT </label>
+
+        <div>
+          <input id="element_1" name="element_1" class="element text medium" type="text" maxlength="255" value=""/>
+        </div>
+      </li>
+      <li id="li_3">
+        <label class="description" for="element_3">SELECT OPTION </label>
+
+        <div>
+          <select class="element select medium" id="element_3" name="element_3">
+            <option value="" selected="selected"></option>
+            <option value="1">First option</option>
+            <option value="2">Second option</option>
+            <option value="3">Third option</option>
+
+          </select>
+        </div>
+      </li>
+      <li id="li_2">
+        <label class="description" for="element_2">Upload a File </label>
+
+        <div>
+          <input id="element_2" name="element_2" class="element file" type="file"/>
+        </div>
+      </li>
+      <li id="li_4">
+        <label class="description" for="element_4">SELECT OPTIONS </label>
+		<span>
+			<input id="element_4_1" name="element_4_1" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_1">First option</label>
+<input id="element_4_2" name="element_4_2" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_2">Second option</label>
+<input id="element_4_3" name="element_4_3" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_3">Third option</label>
+
+		</span>
+      </li>
+
+      <li class="buttons">
+        <input type="hidden" name="form_id" value="1014473"/>
+
+        <input id="saveForm" class="button_text" type="submit" name="saveForm" value="Submit"/>
+      </li>
+    </ul>
+  </form>
+  <div id="footer">
+    Generated by <a href="http://www.phpform.org">pForm</a>
+  </div>
+</div>
+<img id="bottom" src="bottom.png" alt="">
+</body>
+</html>
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/shadow.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/shadow.gif
new file mode 100755
index 0000000..026d52a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/shadow.gif
@@ -0,0 +1 @@
+GIF89a   !   ,        ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/top.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/top.png
new file mode 100755
index 0000000..48749b7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/top.png
@@ -0,0 +1,7 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  3IDATxn0Ф;Ʋ^!M9I ?jk   K?Z  jÀ}]W   L h?FC  @\ctς^ߥu+   ӚCW؛SP   u{
+6'%.>,'  A?JOV@t
+!,  A -)Lװ|'N+'w  c0X55&:_>e\/e>!aT   rp؛o> gz`$L
+    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.css b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.css
new file mode 100755
index 0000000..5272f1c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.css
@@ -0,0 +1,864 @@
+body
+{
+	background:#fffff;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+	margin:8px 0 16px;
+	text-align:center;
+}
+
+#form_container
+{
+	background:#fff;
+	border:1px solid #ccc;
+	margin:0 auto;
+	text-align:left;
+	width:640px;
+}
+
+#top
+{
+	display:block;
+	height:10px;
+	margin:10px auto 0;
+	width:650px;
+}
+
+#footer
+{
+	width:640px;
+	clear:both;
+	color:#999999;
+	text-align:center;
+	width:640px;
+	padding-bottom: 15px;
+	font-size: 85%;
+}
+
+#footer a{
+	color:#999999;
+	text-decoration: none;
+	border-bottom: 1px dotted #999999;
+}
+
+#bottom
+{
+	display:block;
+	height:10px;
+	margin:0 auto;
+	width:650px;
+}
+
+form.appnitro
+{
+	margin:20px 20px 0;
+	padding:0 0 20px;
+}
+
+/**** Logo Section  *****/
+h1
+{
+	background-color:#dedede;
+	margin:0;
+	min-height:0;
+	padding:0;
+	text-decoration:none;
+	text-indent:-8000px;
+
+}
+
+h1 a
+{
+
+	display:block;
+	height:100%;
+	min-height:40px;
+	overflow:hidden;
+}
+
+
+img
+{
+	behavior:url(css/iepngfix.htc);
+	border:none;
+}
+
+
+/**** Form Section ****/
+.appnitro
+{
+	font-family:Lucida Grande, Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+}
+
+.appnitro li
+{
+	width:61%;
+}
+
+form ul
+{
+	font-size:100%;
+	list-style-type:none;
+	margin:0;
+	padding:0;
+	width:100%;
+}
+
+form li
+{
+	display:block;
+	margin:0;
+	padding:4px 5px 2px 9px;
+	position:relative;
+}
+
+form li:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons
+{
+	clear:both;
+	display:block;
+	margin-top:10px;
+}
+
+* html form li
+{
+	height:1%;
+}
+
+* html .buttons
+{
+	height:1%;
+}
+
+* html form li div
+{
+	display:inline-block;
+}
+
+form li div
+{
+	color:#444;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li span
+{
+	color:#444;
+	float:left;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li div.left
+{
+	display:inline;
+	float:left;
+	width:48%;
+}
+
+form li div.right
+{
+	display:inline;
+	float:right;
+	width:48%;
+}
+
+form li div.left .medium
+{
+	width:100%;
+}
+
+form li div.right .medium
+{
+	width:100%;
+}
+
+.clear
+{
+	clear:both;
+}
+
+form li div label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li span label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li .datepicker
+{
+	cursor:pointer !important;
+	float:left;
+	height:16px;
+	margin:.1em 5px 0 0;
+	padding:0;
+	width:16px;
+}
+
+.form_description
+{
+	border-bottom:1px dotted #ccc;
+	clear:both;
+	display:inline-block;
+	margin:0 0 1em;
+}
+
+.form_description[class]
+{
+	display:block;
+}
+
+.form_description h2
+{
+	clear:left;
+	font-size:160%;
+	font-weight:400;
+	margin:0 0 3px;
+}
+
+.form_description p
+{
+	font-size:95%;
+	line-height:130%;
+	margin:0 0 12px;
+}
+
+form hr
+{
+	display:none;
+}
+
+form li.section_break
+{
+	border-top:1px dotted #ccc;
+	margin-top:9px;
+	padding-bottom:0;
+	padding-left:9px;
+	padding-top:13px;
+	width:97% !important;
+}
+
+form ul li.first
+{
+	border-top:none !important;
+	margin-top:0 !important;
+	padding-top:0 !important;
+}
+
+form .section_break h3
+{
+	font-size:110%;
+	font-weight:400;
+	line-height:130%;
+	margin:0 0 2px;
+}
+
+form .section_break p
+{
+	font-size:85%;
+
+	margin:0 0 10px;
+}
+
+/**** Buttons ****/
+input.button_text
+{
+	overflow:visible;
+	padding:0 7px;
+	width:auto;
+}
+
+.buttons input
+{
+	font-size:120%;
+	margin-right:5px;
+}
+
+/**** Inputs and Labels ****/
+label.description
+{
+	border:none;
+	color:#222;
+	display:block;
+	font-size:95%;
+	font-weight:700;
+	line-height:150%;
+	padding:0 0 1px;
+}
+
+span.symbol
+{
+	font-size:115%;
+	line-height:130%;
+}
+
+input.text
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+input.file
+{
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+textarea.textarea
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:100%;
+	margin:0;
+	width:99%;
+}
+
+select.select
+{
+	color:#333;
+	font-size:100%;
+	margin:1px 0;
+	padding:1px 0 0;
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+}
+
+
+input.currency
+{
+	text-align:right;
+}
+
+input.checkbox
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+input.radio
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+label.choice
+{
+	color:#444;
+	display:block;
+	font-size:100%;
+	line-height:1.4em;
+	margin:-1.55em 0 0 25px;
+	padding:4px 0 5px;
+	width:90%;
+}
+
+select.select[class]
+{
+	margin:0;
+	padding:1px 0;
+}
+
+*:first-child+html select.select[class]
+{
+	margin:1px 0;
+}
+
+.safari select.select
+{
+	font-size:120% !important;
+	margin-bottom:1px;
+}
+
+input.small
+{
+	width:25%;
+}
+
+select.small
+{
+	width:25%;
+}
+
+input.medium
+{
+	width:50%;
+}
+
+select.medium
+{
+	width:50%;
+}
+
+input.large
+{
+	width:99%;
+}
+
+select.large
+{
+	width:100%;
+}
+
+textarea.small
+{
+	height:5.5em;
+}
+
+textarea.medium
+{
+	height:10em;
+}
+
+textarea.large
+{
+	height:20em;
+}
+
+/**** Errors ****/
+#error_message
+{
+	background:#fff;
+	border:1px dotted red;
+	margin-bottom:1em;
+	padding-left:0;
+	padding-right:0;
+	padding-top:4px;
+	text-align:center;
+	width:99%;
+}
+
+#error_message_title
+{
+	color:#DF0000;
+	font-size:125%;
+	margin:7px 0 5px;
+	padding:0;
+}
+
+#error_message_desc
+{
+	color:#000;
+	font-size:100%;
+	margin:0 0 .8em;
+}
+
+#error_message_desc strong
+{
+	background-color:#FFDFDF;
+	color:red;
+	padding:2px 3px;
+}
+
+form li.error
+{
+	background-color:#FFDFDF !important;
+	border-bottom:1px solid #EACBCC;
+	border-right:1px solid #EACBCC;
+	margin:3px 0;
+}
+
+form li.error label
+{
+	color:#DF0000 !important;
+}
+
+form p.error
+{
+	clear:both;
+	color:red;
+	font-size:10px;
+	font-weight:700;
+	margin:0 0 5px;
+}
+
+form .required
+{
+	color:red;
+	float:none;
+	font-weight:700;
+}
+
+/**** Guidelines and Error Highlight ****/
+form li.highlighted
+{
+	background-color:#fff7c0;
+}
+
+form .guidelines
+{
+	background:#f5f5f5;
+	border:1px solid #e6e6e6;
+	color:#444;
+	font-size:80%;
+	left:100%;
+	line-height:130%;
+	margin:0 0 0 8px;
+	padding:8px 10px 9px;
+	position:absolute;
+	top:0;
+	visibility:hidden;
+	width:42%;
+	z-index:1000;
+}
+
+form .guidelines small
+{
+	font-size:105%;
+}
+
+form li.highlighted .guidelines
+{
+	visibility:visible;
+}
+
+form li:hover .guidelines
+{
+	visibility:visible;
+}
+
+.no_guidelines .guidelines
+{
+	display:none !important;
+}
+
+.no_guidelines form li
+{
+	width:97%;
+}
+
+.no_guidelines li.section
+{
+	padding-left:9px;
+}
+
+/*** Success Message ****/
+.form_success
+{
+	clear: both;
+	margin: 0;
+	padding: 90px 0pt 100px;
+	text-align: center
+}
+
+.form_success h2 {
+    clear:left;
+    font-size:160%;
+    font-weight:normal;
+    margin:0pt 0pt 3px;
+}
+
+/*** Password ****/
+ul.password{
+    margin-top:60px;
+    margin-bottom: 60px;
+    text-align: center;
+}
+.password h2{
+    color:#DF0000;
+    font-weight:bold;
+    margin:0pt auto 10px;
+}
+
+.password input.text {
+   font-size:170% !important;
+   width:380px;
+   text-align: center;
+}
+.password label{
+   display:block;
+   font-size:120% !important;
+   padding-top:10px;
+   font-weight:bold;
+}
+
+#li_captcha{
+   padding-left: 5px;
+}
+
+
+#li_captcha span{
+	float:none;
+}
+
+/** Embedded Form **/
+
+.embed #form_container{
+	border: none;
+}
+
+.embed #top, .embed #bottom, .embed h1{
+	display: none;
+}
+
+.embed #form_container{
+	width: 100%;
+}
+
+.embed #footer{
+	text-align: left;
+	padding-left: 10px;
+	width: 99%;
+}
+
+.embed #footer.success{
+	text-align: center;
+}
+
+.embed form.appnitro
+{
+	margin:0px 0px 0;
+
+}
+
+
+
+/*** Calendar **********************/
+div.calendar { position: relative; }
+
+.calendar table {
+cursor:pointer;
+border:1px solid #ccc;
+font-size: 11px;
+color: #000;
+background: #fff;
+font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+}
+
+.calendar .button {
+text-align: center;
+padding: 2px;
+}
+
+.calendar .nav {
+background:#f5f5f5;
+}
+
+.calendar thead .title {
+font-weight: bold;
+text-align: center;
+background: #dedede;
+color: #000;
+padding: 2px 0 3px 0;
+}
+
+.calendar thead .headrow {
+background: #f5f5f5;
+color: #444;
+font-weight:bold;
+}
+
+.calendar thead .daynames {
+background: #fff;
+color:#333;
+font-weight:bold;
+}
+
+.calendar thead .name {
+border-bottom: 1px dotted #ccc;
+padding: 2px;
+text-align: center;
+color: #000;
+}
+
+.calendar thead .weekend {
+color: #666;
+}
+
+.calendar thead .hilite {
+background-color: #444;
+color: #fff;
+padding: 1px;
+}
+
+.calendar thead .active {
+background-color: #d12f19;
+color:#fff;
+padding: 2px 0px 0px 2px;
+}
+
+
+.calendar tbody .day {
+width:1.8em;
+color: #222;
+text-align: right;
+padding: 2px 2px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+font-size: 80%;
+color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+color: #fbb;
+}
+
+.calendar table .wn {
+padding: 2px 2px 2px 2px;
+border-right: 1px solid #000;
+background: #666;
+}
+
+.calendar tbody .rowhilite td {
+background: #FFF1AF;
+}
+
+.calendar tbody .rowhilite td.wn {
+background: #FFF1AF;
+}
+
+.calendar tbody td.hilite {
+padding: 1px 1px 1px 1px;
+background:#444 !important;
+color:#fff !important;
+}
+
+.calendar tbody td.active {
+color:#fff;
+background: #529214 !important;
+padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected {
+font-weight: bold;
+border: 1px solid #888;
+padding: 1px 1px 1px 1px;
+background: #f5f5f5 !important;
+color: #222 !important;
+}
+
+.calendar tbody td.weekend {
+color: #666;
+}
+
+.calendar tbody td.today {
+font-weight: bold;
+color: #529214;
+background:#D9EFC2;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell {
+visibility: hidden;
+}
+
+.calendar tbody .emptyrow {
+display: none;
+}
+
+.calendar tfoot .footrow {
+text-align: center;
+background: #556;
+color: #fff;
+}
+
+.calendar tfoot .ttip {
+background: #222;
+color: #fff;
+font-size:10px;
+border-top: 1px solid #dedede;
+padding: 3px;
+}
+
+.calendar tfoot .hilite {
+background: #aaf;
+border: 1px solid #04f;
+color: #000;
+padding: 1px;
+}
+
+.calendar tfoot .active {
+background: #77c;
+padding: 2px 0px 0px 2px;
+}
+
+.calendar .combo {
+position: absolute;
+display: none;
+top: 0px;
+left: 0px;
+width: 4em;
+border: 1px solid #ccc;
+background: #f5f5f5;
+color: #222;
+font-size: 90%;
+z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+text-align: center;
+padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+width: 4em;
+}
+
+.calendar .combo .hilite {
+background: #444;
+color:#fff;
+}
+
+.calendar .combo .active {
+border-top: 1px solid #999;
+border-bottom: 1px solid #999;
+background: #dedede;
+font-weight: bold;
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.js b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.js
new file mode 100755
index 0000000..d3a87e2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_execute/view.js
@@ -0,0 +1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('3(7.X){7["R"+a]=a;7["z"+a]=6(){7["R"+a](7.1k)};7.X("1e",7["z"+a])}E{7.19("z",a,15)}2 j=H V();6 a(){2 e=q.1d("1a");3(e){o(e,"P");2 N=B(q,"*","14");3((e.12<=10)||(N=="")){c(e,"P",d)}}4=B(q,"*","1n");k(i=0;i<4.b;i++){3(4[i].F=="1g"||4[i].F=="1f"||4[i].F=="1c"){4[i].1b=6(){r();c(v.5.5,"f",d)};4[i].O=6(){r();c(v.5.5,"f",d)};j.D(j.b,0,4[i])}E{4[i].O=6(){r();c(v.5.5,"f",d)};4[i].18=6(){o(v.5.5,"f")}}}2 C=17.16.13();2 A=q.M("11");3(C.K("J")+1){c(A[0],"J",d)}3(C.K("I")+1){c(A[0],"I",d)}}6 r(){k(2 i=0;i<j.b;i++){o(j[i].5.5,"f")}}6 B(m,y,w){2 x=(y=="*"&&m.Y)?m.Y:m.M(y);2 G=H V();w=w.1m(/\\-/g,"\\\\-");2 L=H 1l("(^|\\\\s)"+w+"(\\\\s|$)");2 n;k(2 i=0;i<x.b;i++){n=x[i];3(L.1j(n.8)){G.1i(n)}}1h(G)}6 o(p,T){3(p.8){2 h=p.8.Z(" ");2 U=T.t();k(2 i=0;i<h.b;i++){3(h[i].t()==U){h.D(i,1);i--}}p.8=h.S(" ")}}6 c(l,u,Q){3(l.8){2 9=l.8.Z(" ");3(Q){2 W=u.t();k(2 i=0;i<9.b;i++){3(9[i].t()==W){9.D(i,1);i--}}}9[9.b]=u;l.8=9.S(" ")}E{l.8=u}}',62,86,'||var|if|elements|parentNode|function|window|className|_16|initialize|length|addClassName|true|_1|highlighted||_10||el_array|for|_13|_6|_c|removeClassName|_e|document|safari_reset||toUpperCase|_14|this|_8|_9|_7|load|_4|getElementsByClassName|_3|splice|else|type|_a|new|firefox|safari|indexOf|_b|getElementsByTagName|_2|onfocus|no_guidelines|_15|event_load|join|_f|_11|Array|_17|attachEvent|all|split|450|body|offsetWidth|toLowerCase|guidelines|false|userAgent|navigator|onblur|addEventListener|main_body|onclick|file|getElementById|onload|radio|checkbox|return|push|test|event|RegExp|replace|element'.split('|'),0,{}))
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/blank.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/blank.gif
new file mode 100755
index 0000000..75b945d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/blank.gif
@@ -0,0 +1 @@
+GIF89a         !   ,       T ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/bottom.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/bottom.png
new file mode 100755
index 0000000..7f46c80
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/bottom.png
@@ -0,0 +1,6 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  AIDATxQO02?WTH䳖e>M<'|ϽdS)%MҞ˖n\R  <GY,5ۖùv~_.~rW."   -m]f%
+@A8*y, 0)  pZ(2Ĥ|0Ak   _;`Tw5   畁~>J9*6xPc	P  /8зç=zX	  @eP hѣAAs?  b{s   ~   |
+0 %X    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/extension.js b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/extension.js
new file mode 100644
index 0000000..72dc3dd
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/extension.js
@@ -0,0 +1,5 @@
+function injectedFunction(documentForm) {
+  document.getElementById("element_1").value = "THIS_IS_SPARTA";
+  document.getElementById("element_3").selectedIndex = 1;
+  return documentForm.submit();
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/form.html b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/form.html
new file mode 100755
index 0000000..b7b4480
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/form.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>TEST FORM</title>
+  <link rel="stylesheet" type="text/css" href="view.css" media="all">
+</head>
+<body id="main_body">
+
+<img id="top" src="top.png" alt="">
+
+<div id="form_container">
+
+  <h1><a>TEST FORM</a></h1>
+
+  <form id="form_1014473" class="appnitro" enctype="multipart/form-data" method="post" action="/check-post-request/" onsubmit="return injectedFunction(this);">
+    <div class="form_description">
+      <h2>TEST FORM</h2>
+
+      <p>Test form for testing purposes</p>
+    </div>
+    <ul>
+
+      <li id="li_1">
+        <label class="description" for="element_1">TYPE YOUR TEXT </label>
+
+        <div>
+          <input id="element_1" name="element_1" class="element text medium" type="text" maxlength="255" value=""/>
+        </div>
+      </li>
+      <li id="li_3">
+        <label class="description" for="element_3">SELECT OPTION </label>
+
+        <div>
+          <select class="element select medium" id="element_3" name="element_3">
+            <option value="" selected="selected"></option>
+            <option value="1">First option</option>
+            <option value="2">Second option</option>
+            <option value="3">Third option</option>
+
+          </select>
+        </div>
+      </li>
+      <li id="li_2">
+        <label class="description" for="element_2">Upload a File </label>
+
+        <div>
+          <input id="element_2" name="element_2" class="element file" type="file"/>
+        </div>
+      </li>
+      <li id="li_4">
+        <label class="description" for="element_4">SELECT OPTIONS </label>
+		<span>
+			<input id="element_4_1" name="element_4_1" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_1">First option</label>
+<input id="element_4_2" name="element_4_2" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_2">Second option</label>
+<input id="element_4_3" name="element_4_3" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_3">Third option</label>
+
+		</span>
+      </li>
+
+      <li class="buttons">
+        <input type="hidden" name="form_id" value="1014473"/>
+
+        <input id="saveForm" class="button_text" type="submit" name="saveForm" value="Submit"/>
+      </li>
+    </ul>
+  </form>
+  <div id="footer">
+    Generated by <a href="http://www.phpform.org">pForm</a>
+  </div>
+</div>
+<img id="bottom" src="bottom.png" alt="">
+</body>
+</html>
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/shadow.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/shadow.gif
new file mode 100755
index 0000000..026d52a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/shadow.gif
@@ -0,0 +1 @@
+GIF89a   !   ,        ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/top.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/top.png
new file mode 100755
index 0000000..48749b7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/top.png
@@ -0,0 +1,7 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  3IDATxn0Ф;Ʋ^!M9I ?jk   K?Z  jÀ}]W   L h?FC  @\ctς^ߥu+   ӚCW؛SP   u{
+6'%.>,'  A?JOV@t
+!,  A -)Lװ|'N+'w  c0X55&:_>e\/e>!aT   rp؛o> gz`$L
+    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/view.css b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/view.css
new file mode 100755
index 0000000..5272f1c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/script_extensions/view.css
@@ -0,0 +1,864 @@
+body
+{
+	background:#fffff;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+	margin:8px 0 16px;
+	text-align:center;
+}
+
+#form_container
+{
+	background:#fff;
+	border:1px solid #ccc;
+	margin:0 auto;
+	text-align:left;
+	width:640px;
+}
+
+#top
+{
+	display:block;
+	height:10px;
+	margin:10px auto 0;
+	width:650px;
+}
+
+#footer
+{
+	width:640px;
+	clear:both;
+	color:#999999;
+	text-align:center;
+	width:640px;
+	padding-bottom: 15px;
+	font-size: 85%;
+}
+
+#footer a{
+	color:#999999;
+	text-decoration: none;
+	border-bottom: 1px dotted #999999;
+}
+
+#bottom
+{
+	display:block;
+	height:10px;
+	margin:0 auto;
+	width:650px;
+}
+
+form.appnitro
+{
+	margin:20px 20px 0;
+	padding:0 0 20px;
+}
+
+/**** Logo Section  *****/
+h1
+{
+	background-color:#dedede;
+	margin:0;
+	min-height:0;
+	padding:0;
+	text-decoration:none;
+	text-indent:-8000px;
+
+}
+
+h1 a
+{
+
+	display:block;
+	height:100%;
+	min-height:40px;
+	overflow:hidden;
+}
+
+
+img
+{
+	behavior:url(css/iepngfix.htc);
+	border:none;
+}
+
+
+/**** Form Section ****/
+.appnitro
+{
+	font-family:Lucida Grande, Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+}
+
+.appnitro li
+{
+	width:61%;
+}
+
+form ul
+{
+	font-size:100%;
+	list-style-type:none;
+	margin:0;
+	padding:0;
+	width:100%;
+}
+
+form li
+{
+	display:block;
+	margin:0;
+	padding:4px 5px 2px 9px;
+	position:relative;
+}
+
+form li:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons
+{
+	clear:both;
+	display:block;
+	margin-top:10px;
+}
+
+* html form li
+{
+	height:1%;
+}
+
+* html .buttons
+{
+	height:1%;
+}
+
+* html form li div
+{
+	display:inline-block;
+}
+
+form li div
+{
+	color:#444;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li span
+{
+	color:#444;
+	float:left;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li div.left
+{
+	display:inline;
+	float:left;
+	width:48%;
+}
+
+form li div.right
+{
+	display:inline;
+	float:right;
+	width:48%;
+}
+
+form li div.left .medium
+{
+	width:100%;
+}
+
+form li div.right .medium
+{
+	width:100%;
+}
+
+.clear
+{
+	clear:both;
+}
+
+form li div label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li span label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li .datepicker
+{
+	cursor:pointer !important;
+	float:left;
+	height:16px;
+	margin:.1em 5px 0 0;
+	padding:0;
+	width:16px;
+}
+
+.form_description
+{
+	border-bottom:1px dotted #ccc;
+	clear:both;
+	display:inline-block;
+	margin:0 0 1em;
+}
+
+.form_description[class]
+{
+	display:block;
+}
+
+.form_description h2
+{
+	clear:left;
+	font-size:160%;
+	font-weight:400;
+	margin:0 0 3px;
+}
+
+.form_description p
+{
+	font-size:95%;
+	line-height:130%;
+	margin:0 0 12px;
+}
+
+form hr
+{
+	display:none;
+}
+
+form li.section_break
+{
+	border-top:1px dotted #ccc;
+	margin-top:9px;
+	padding-bottom:0;
+	padding-left:9px;
+	padding-top:13px;
+	width:97% !important;
+}
+
+form ul li.first
+{
+	border-top:none !important;
+	margin-top:0 !important;
+	padding-top:0 !important;
+}
+
+form .section_break h3
+{
+	font-size:110%;
+	font-weight:400;
+	line-height:130%;
+	margin:0 0 2px;
+}
+
+form .section_break p
+{
+	font-size:85%;
+
+	margin:0 0 10px;
+}
+
+/**** Buttons ****/
+input.button_text
+{
+	overflow:visible;
+	padding:0 7px;
+	width:auto;
+}
+
+.buttons input
+{
+	font-size:120%;
+	margin-right:5px;
+}
+
+/**** Inputs and Labels ****/
+label.description
+{
+	border:none;
+	color:#222;
+	display:block;
+	font-size:95%;
+	font-weight:700;
+	line-height:150%;
+	padding:0 0 1px;
+}
+
+span.symbol
+{
+	font-size:115%;
+	line-height:130%;
+}
+
+input.text
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+input.file
+{
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+textarea.textarea
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:100%;
+	margin:0;
+	width:99%;
+}
+
+select.select
+{
+	color:#333;
+	font-size:100%;
+	margin:1px 0;
+	padding:1px 0 0;
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+}
+
+
+input.currency
+{
+	text-align:right;
+}
+
+input.checkbox
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+input.radio
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+label.choice
+{
+	color:#444;
+	display:block;
+	font-size:100%;
+	line-height:1.4em;
+	margin:-1.55em 0 0 25px;
+	padding:4px 0 5px;
+	width:90%;
+}
+
+select.select[class]
+{
+	margin:0;
+	padding:1px 0;
+}
+
+*:first-child+html select.select[class]
+{
+	margin:1px 0;
+}
+
+.safari select.select
+{
+	font-size:120% !important;
+	margin-bottom:1px;
+}
+
+input.small
+{
+	width:25%;
+}
+
+select.small
+{
+	width:25%;
+}
+
+input.medium
+{
+	width:50%;
+}
+
+select.medium
+{
+	width:50%;
+}
+
+input.large
+{
+	width:99%;
+}
+
+select.large
+{
+	width:100%;
+}
+
+textarea.small
+{
+	height:5.5em;
+}
+
+textarea.medium
+{
+	height:10em;
+}
+
+textarea.large
+{
+	height:20em;
+}
+
+/**** Errors ****/
+#error_message
+{
+	background:#fff;
+	border:1px dotted red;
+	margin-bottom:1em;
+	padding-left:0;
+	padding-right:0;
+	padding-top:4px;
+	text-align:center;
+	width:99%;
+}
+
+#error_message_title
+{
+	color:#DF0000;
+	font-size:125%;
+	margin:7px 0 5px;
+	padding:0;
+}
+
+#error_message_desc
+{
+	color:#000;
+	font-size:100%;
+	margin:0 0 .8em;
+}
+
+#error_message_desc strong
+{
+	background-color:#FFDFDF;
+	color:red;
+	padding:2px 3px;
+}
+
+form li.error
+{
+	background-color:#FFDFDF !important;
+	border-bottom:1px solid #EACBCC;
+	border-right:1px solid #EACBCC;
+	margin:3px 0;
+}
+
+form li.error label
+{
+	color:#DF0000 !important;
+}
+
+form p.error
+{
+	clear:both;
+	color:red;
+	font-size:10px;
+	font-weight:700;
+	margin:0 0 5px;
+}
+
+form .required
+{
+	color:red;
+	float:none;
+	font-weight:700;
+}
+
+/**** Guidelines and Error Highlight ****/
+form li.highlighted
+{
+	background-color:#fff7c0;
+}
+
+form .guidelines
+{
+	background:#f5f5f5;
+	border:1px solid #e6e6e6;
+	color:#444;
+	font-size:80%;
+	left:100%;
+	line-height:130%;
+	margin:0 0 0 8px;
+	padding:8px 10px 9px;
+	position:absolute;
+	top:0;
+	visibility:hidden;
+	width:42%;
+	z-index:1000;
+}
+
+form .guidelines small
+{
+	font-size:105%;
+}
+
+form li.highlighted .guidelines
+{
+	visibility:visible;
+}
+
+form li:hover .guidelines
+{
+	visibility:visible;
+}
+
+.no_guidelines .guidelines
+{
+	display:none !important;
+}
+
+.no_guidelines form li
+{
+	width:97%;
+}
+
+.no_guidelines li.section
+{
+	padding-left:9px;
+}
+
+/*** Success Message ****/
+.form_success
+{
+	clear: both;
+	margin: 0;
+	padding: 90px 0pt 100px;
+	text-align: center
+}
+
+.form_success h2 {
+    clear:left;
+    font-size:160%;
+    font-weight:normal;
+    margin:0pt 0pt 3px;
+}
+
+/*** Password ****/
+ul.password{
+    margin-top:60px;
+    margin-bottom: 60px;
+    text-align: center;
+}
+.password h2{
+    color:#DF0000;
+    font-weight:bold;
+    margin:0pt auto 10px;
+}
+
+.password input.text {
+   font-size:170% !important;
+   width:380px;
+   text-align: center;
+}
+.password label{
+   display:block;
+   font-size:120% !important;
+   padding-top:10px;
+   font-weight:bold;
+}
+
+#li_captcha{
+   padding-left: 5px;
+}
+
+
+#li_captcha span{
+	float:none;
+}
+
+/** Embedded Form **/
+
+.embed #form_container{
+	border: none;
+}
+
+.embed #top, .embed #bottom, .embed h1{
+	display: none;
+}
+
+.embed #form_container{
+	width: 100%;
+}
+
+.embed #footer{
+	text-align: left;
+	padding-left: 10px;
+	width: 99%;
+}
+
+.embed #footer.success{
+	text-align: center;
+}
+
+.embed form.appnitro
+{
+	margin:0px 0px 0;
+
+}
+
+
+
+/*** Calendar **********************/
+div.calendar { position: relative; }
+
+.calendar table {
+cursor:pointer;
+border:1px solid #ccc;
+font-size: 11px;
+color: #000;
+background: #fff;
+font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+}
+
+.calendar .button {
+text-align: center;
+padding: 2px;
+}
+
+.calendar .nav {
+background:#f5f5f5;
+}
+
+.calendar thead .title {
+font-weight: bold;
+text-align: center;
+background: #dedede;
+color: #000;
+padding: 2px 0 3px 0;
+}
+
+.calendar thead .headrow {
+background: #f5f5f5;
+color: #444;
+font-weight:bold;
+}
+
+.calendar thead .daynames {
+background: #fff;
+color:#333;
+font-weight:bold;
+}
+
+.calendar thead .name {
+border-bottom: 1px dotted #ccc;
+padding: 2px;
+text-align: center;
+color: #000;
+}
+
+.calendar thead .weekend {
+color: #666;
+}
+
+.calendar thead .hilite {
+background-color: #444;
+color: #fff;
+padding: 1px;
+}
+
+.calendar thead .active {
+background-color: #d12f19;
+color:#fff;
+padding: 2px 0px 0px 2px;
+}
+
+
+.calendar tbody .day {
+width:1.8em;
+color: #222;
+text-align: right;
+padding: 2px 2px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+font-size: 80%;
+color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+color: #fbb;
+}
+
+.calendar table .wn {
+padding: 2px 2px 2px 2px;
+border-right: 1px solid #000;
+background: #666;
+}
+
+.calendar tbody .rowhilite td {
+background: #FFF1AF;
+}
+
+.calendar tbody .rowhilite td.wn {
+background: #FFF1AF;
+}
+
+.calendar tbody td.hilite {
+padding: 1px 1px 1px 1px;
+background:#444 !important;
+color:#fff !important;
+}
+
+.calendar tbody td.active {
+color:#fff;
+background: #529214 !important;
+padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected {
+font-weight: bold;
+border: 1px solid #888;
+padding: 1px 1px 1px 1px;
+background: #f5f5f5 !important;
+color: #222 !important;
+}
+
+.calendar tbody td.weekend {
+color: #666;
+}
+
+.calendar tbody td.today {
+font-weight: bold;
+color: #529214;
+background:#D9EFC2;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell {
+visibility: hidden;
+}
+
+.calendar tbody .emptyrow {
+display: none;
+}
+
+.calendar tfoot .footrow {
+text-align: center;
+background: #556;
+color: #fff;
+}
+
+.calendar tfoot .ttip {
+background: #222;
+color: #fff;
+font-size:10px;
+border-top: 1px solid #dedede;
+padding: 3px;
+}
+
+.calendar tfoot .hilite {
+background: #aaf;
+border: 1px solid #04f;
+color: #000;
+padding: 1px;
+}
+
+.calendar tfoot .active {
+background: #77c;
+padding: 2px 0px 0px 2px;
+}
+
+.calendar .combo {
+position: absolute;
+display: none;
+top: 0px;
+left: 0px;
+width: 4em;
+border: 1px solid #ccc;
+background: #f5f5f5;
+color: #222;
+font-size: 90%;
+z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+text-align: center;
+padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+width: 4em;
+}
+
+.calendar .combo .hilite {
+background: #444;
+color:#fff;
+}
+
+.calendar .combo .active {
+border-top: 1px solid #999;
+border-bottom: 1px solid #999;
+background: #dedede;
+font-weight: bold;
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/blank.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/blank.gif
new file mode 100755
index 0000000..75b945d
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/blank.gif
@@ -0,0 +1 @@
+GIF89a         !   ,       T ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/bottom.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/bottom.png
new file mode 100755
index 0000000..7f46c80
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/bottom.png
@@ -0,0 +1,6 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  AIDATxQO02?WTH䳖e>M<'|ϽdS)%MҞ˖n\R  <GY,5ۖùv~_.~rW."   -m]f%
+@A8*y, 0)  pZ(2Ĥ|0Ak   _;`Tw5   畁~>J9*6xPc	P  /8зç=zX	  @eP hѣAAs?  b{s   ~   |
+0 %X    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/form.html b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/form.html
new file mode 100755
index 0000000..00ab833
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/form.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>TEST FORM</title>
+  <link rel="stylesheet" type="text/css" href="view.css" media="all">
+  <script type="text/javascript" src="view.js"></script>
+
+</head>
+<body id="main_body">
+
+<img id="top" src="top.png" alt="">
+
+<div id="form_container">
+
+  <h1><a>TEST FORM</a></h1>
+
+  <form id="form_1014473" class="appnitro" enctype="multipart/form-data" method="post" action="/check-post-request/" onsubmit="return this.submit();">
+    <div class="form_description">
+      <h2>TEST FORM</h2>
+
+      <p>Test form for testing purposes</p>
+    </div>
+    <ul>
+
+      <li id="li_1">
+        <label class="description" for="element_1">TYPE YOUR TEXT </label>
+
+        <div>
+          <input id="element_1" name="element_1" class="element text medium" type="text" maxlength="255" value=""/>
+        </div>
+      </li>
+      <li id="li_3">
+        <label class="description" for="element_3">SELECT OPTION </label>
+
+        <div>
+          <select class="element select medium" id="element_3" name="element_3">
+            <option value="" selected="selected"></option>
+            <option value="1">First option</option>
+            <option value="2">Second option</option>
+            <option value="3">Third option</option>
+
+          </select>
+        </div>
+      </li>
+      <li id="li_2">
+        <label class="description" for="element_2">Upload a File </label>
+
+        <div>
+          <input id="element_2" name="element_2" class="element file" type="file"/>
+        </div>
+      </li>
+      <li id="li_4">
+        <label class="description" for="element_4">SELECT OPTIONS </label>
+		<span>
+			<input id="element_4_1" name="element_4_1" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_1">First option</label>
+<input id="element_4_2" name="element_4_2" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_2">Second option</label>
+<input id="element_4_3" name="element_4_3" class="element checkbox" type="checkbox" value="1"/>
+<label class="choice" for="element_4_3">Third option</label>
+
+		</span>
+      </li>
+
+      <li class="buttons">
+        <input type="hidden" name="form_id" value="1014473"/>
+
+        <input id="saveForm" class="button_text" type="submit" name="saveForm" value="Submit"/>
+      </li>
+    </ul>
+  </form>
+  <div id="footer">
+    Generated by <a href="http://www.phpform.org">pForm</a>
+  </div>
+</div>
+<img id="bottom" src="bottom.png" alt="">
+</body>
+</html>
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/image_for_test.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/image_for_test.png
new file mode 100644
index 0000000..fadead9
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/image_for_test.png
@@ -0,0 +1,54 @@
+PNG
+
+   IHDR     )   <nJG   sBIT|d   	pHYs         IDATxy\Ue/!(f{jbii-M6R*mK9fi$ecV(.i.Ⴀ p~8ܟnEzׅs휖>                                                                 wk (#oF   ,F1ao VdS   my˯,`
+Lj`V67g<  3nzKy
++ `FMm   I~MY97t;B 
+ }/[   ʶ&ٯnÔ `fٓwn>~s@    P6ݼ;.S\X eٓ
+{ey    ({g'7+ `R 0Ϟ;>   P1OWr1 `VA$g   @tu#;Nʹ/  H{  L(,,yL 05WWWk   ())C             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	[{ 5իWC   @)JJJ a      &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      ` P=zTŪ$;;;߿rrss~)mڴIj׮@vi,o0b3[rr٣]tI͚5Spp|}}ܗTjBڪq:$)  @    ܁v]w?WZ޳{+V̳իX
+RqadffoQB<HHH(V_Ƽy
+SjRѣ  CTTQ"Lw V  %hܸqJHHql֭Zz5r\uU^{M...ʵmV^xAիWԩSճgO_~ʕ+-X
+R8%իٳ{ծ]['OV˖-⢨(-X@j֭֬[<6muYmܸQ{ѨQg}61.]TwuW9~    Cݎ y5k!ܹsen:..wnH2"""nX
+RqaL4ɐd4m8uTȑ#-e222̙39>ܐdԫW/988///#333ϱM8ѐdY
+   ́ 0360FYy$;y}]I̙3啫{=ժUKG}T|II7EFFF5R:uh͹O?T͚5w1
+   s  wnݺIf̘QMJӿ/*,,L۷Ϸƌ#I;wnھp$)$$$Mmmm5p@I7-[(66V?x    .;;;m߾]w}~gk)O$lٲвe=B/^XԽ{|4Hj*]v-ǹ%K    f&@O)Ǳ~Z]v$5iD+WTxx߯{Wֻ+wwR9srmةS'F~$թSж
+ڵk:y֭|ZZ>|XV^6m乑aƍ+88X111T߾}%I)))/չsgժU    2f߾}qϻKkɒ%ڰa>#=#2ӧ:VjUK piI[m\ٳ?Ǐ״irݧ?WLL.]j	 ֮]˗/+t\   @yB  11;XBcƌӧ裏/P>}n8w)OOnZu*>>^us?O%iСrvv֚5kZj:hܸq믕$WWW}'V~B   	{  w~jݺ2335cƌRvT弿P2UVGo}}ǒ_~Y/]///kNZbi&/c   @yG  ...%?+Hjڴ$SfffeH*taÆڵk8p`.Ʒ,]TYYY   S"  ʉ 0]tѰaTfM;vL+Vȷ܅`I7/̌3}_.|>}۷k٪_ﾢ_   PN  w;vy|ѢEʒJyTUZUSN$_rp+WCٳg~`Μ9-ߣGSN   LM 2楗^,}6777zTNWM6֮]kJ,X [۲6LԬY'=ܣʕ++::Z˖-ӱcԲeKˤ(:t蠑#Gj:tݫ5k[><<\W\   q 2fyo&M(&&FoF6ԛoY* PTvvv9sڷoѣGkܹ;w弋&NW_}U;Ｃ7z'oٞ={*##㖯   (l= ZPk$''k޽:}U~}ڽ{ԬY3Z{H  d<
+ TZ5i(5jcǎ   `JeAa     p[      `             &@      	      `        P\~]K.Ր!Co    EG  {GSz5jX~3gm722R3g3<OY뻨n:k   *{k (O~gwuF~/{kȑ8p`[kN-Z}WI]صJ;vPʕKd|    @)զMO>r\ddZlիkذaW_}U;ww(###G{3fE)  @OVlltի+((H+VڵVX:X&]vW_}>}]ǏٳgաCyzzjy^KI}$נAm6"##5p@yxxh̘1>OnnnzիWY׮]j*=C^cl{-wA[n-AAt_R]   r)**ʸ5kf̛7/Ν;3f0Ν;/60f?~0øt钥3gjF~{8s挑aÆ3Ν;gXpvv60 Xjc_~eCѪU+o1VXa鷥u%q򳏏1|cΝ믿a>O.\0~X`A|ƍk֬1[nݺ9ʺ?w???cÆ3ȯ|k~V  feXPZX ˗/kСժU+m޼Ydcc#[N[$J*PU&\mV\Y+VTJ={(&&F&L-ZO>7nzǫO>Ure+ܹv/ӷlq$i1bZh ޽[.]֭[5i$]vjܸlll9yd=#رM6[{|k~V   7 m۶Mׯ_װa4h _^iii˗/_-mܸ8yUcR'OZ~]vz5kִlkk+wwwϷܮհaCn:u'''ρ:p@uk}(   pga@ըQCZv<==s		5cG			X$0ZCJHHL>,__ߒwv]wҥK:{<<<"?t233%īWk<7W}>{   @)j޼o+55UtEIRzz/{{{<X,֩Sȟ.G~o;vgϞJ4W6駟ֺu4p@%$$b֭2CoձcG9???ٳG/_uBǐ_{í;   _ @)ʕ+eyzz_$k׮_ڵk)SE4|p-\Paaa?\f͒BBBϫ[nJ4͖,Yk׮S)gg|˟8qB5R㣇zrgդI{Wޅ_{í;   _nsQQQFhh?*Ukubb\\\dggu%3gΨF/}Jy]kiܯ%$$vڊRppp󁁁>}t+W5W[(HQړ ,::Zaaã`J XI5<߄B
+Ş֪U*)Ｎ4ecc#\Rzs3|'kAZ{     gܸqڻwΝ;y};w7n\b}t{   eG    Px f&            &@      	      `             &@      	      `             &@      	  %(::wywyz@ɰ  k2BCC=   h1)                   0      L                    0      L                    0      L                    0      L                    0      L  (eڵS}EDD~?5rHIR-,4iݻkΝef͚ks-ׯk֬Yjڴ6l+44TK,QVVz\t
+SO=={=:~x׹yfկ__=zPff$ȑ#
+7|S   O?%IcƌѯH999Ij֬7j߿mܸҎ>#رC9s-[&Irww,IOj>L#GԨQtu2eRSS5bĈb]{PPO;v7Եk״dɒb#|E۷>L7nxuE7.R   @y
+ O~~~X$nݺc+WK/4ۺz|M=S9&*TP@@*wwwڵ򩼝=???Siٲe\ƎJ*Elll4gըQ#:TUTӧox'O75eʔb   7A=z'|+99Yvvve7К5k͛FUVI*WΝ;*++Ks,uW 8p@6mZ2CZb._Cn^N>\sտ/Y_     e&NG}T۶moY-..N!!!z<<<4uT˹+WhΜ9$oo|9w$A5h@PBBBkϞ=jӦ$izU
+k޼y2C7K     PF5o\:v뼋^|Eyzz*44TΖsڼy_[ݻsWZZZ29VH7gLqt|*Vo   <` ^ҟ*UjӦMIӸqc988h׮]9&񊋋ԩ#ITll^*RÇVZ9ڬ\ڵkzJׇ~Ɲ>Ӝ9st]wiΝ96P   ̈  (?Z^}^AO*==]Ǐי3gt	?^4vX9::JZj,͞=[Zp._<~ꩧTV-͞=[G-ĉթS'kN&M$曖   3 pႆby// ggg}ꫯ$x7P-ƏT^Z.VXQƍرc/kŊ(@^ٻwo=zT|XϞ=n:-^XÇ/v?   @y`c eZ{w,<yRNNN0/=  pDGG+,,yL  
+ekkuZƦH    >            0      L       `@e/= XӴiӬ=  P RֹsgUX `b   
+BCC= &m6k  X{      `             & lܸQ)))ҥv)7775i$ǹJ$m޼Yjܸ$)33Sv!www5iҤHT;۲edccU*00P9'&&j֭j۶\]]sˮ/I^^^j޼*T h4{Ƃ    # J~7%''K~W%%%M6>-Z\믿K h"Y&/.^:ȑ#:p@&%NA-Ztխ[Wӧ5}tkR/ТEt91"۷O.]Ҝ9st]wf    G ={ｧ{jݸ8رC6lP5JԻ{sI~iZ dffjݺuպu4|p[?<<\_|"Ӣ   732C;vJ;ŭWn]?~?,EDDٳ_fM]vXc,n   @y@  qիs
+RfM2DӦMӔ)St[V)N8mذAZ[fzFQFZvmu/\6觟~R=umE   (O 2:tPaygjǎ8psK}j;۴i޽{e˖z$IgΜO?GyDԫW/mݺղ_?\:uɓ5sL=E   7 q-[lt´mV͛7ɓ5j(?sv
+'>֭SJ|rIRjj222~z<RGՐ!C4dmٲE-[,   a PUZU?Ro'zj޼yVV֮]zHSzԨQ#jJ_}Uv+T WWWk/m۶"8}    + rpႚ5k&{{{ܹSvvvSNsvءt=Z*ToԨ{1(8Ns    IDAT88G_SLV}     PΜ<yR&LjԨ*Uhԩ*vnիգGs_k׮srjzW5gؔx   @y_@9ez۶mS֭okYYYSeNI(@Y0A0%V  吭s1bD{ݻX;   `= y/   C  j   @k     0V  Vp1k     X   G      0      L                    0      L                    0      L                    0      L                    0{k 09shҥJLLTVVZh˗KZhy=Z-Zoߞ͂]vMעEtU)33SիWWTTڵk|ǻi&իW/Ǳׯk޼yZp],h
+mqUXQuɓ5nܸBׯ_-Nao#F~o    Pʎ;ٳgo߾zWu9:u*Gwww-[{$0՛2eVXp=s^9J>S]~]4f믊$'W_4rH5Jׯ_믿)S(55U#FG};v(""B3gμ}^pAfϞ]{   w @);uꔲ ϯmUĉZr6l^{M666@J9ٮX$nݺrvvγ8-[L+Wرcegg'IzWf͙3GԯPh׮]|b;tppս{wuԩw   ( (e{ԩ^zүLjjVZUViƍEn;za{-=cKRT9dddhʕJHHP&M,磏>5kjɺx-^)   P~  ^k֬Q֭oo߾ZfM2W\ќ9s4g}En;z			Sp9IRr>ݯtNzZ&OEDD?    
+Uŋkɒ%z74n8rsstY͛7ݼyyyI~GKu$oooIRLLLswQF\\\/S./>%k׮zf9::O   <a `%666zիW/eeeiΝF^Μ9S"m6nXڵk,'թSrJ*߿ڴisK[3۔)Sl    R˗+..Nv%կ_R&--M?gϞ|e_=2D4bO׆4aO*==]Ǐי3gt	?^4vV]v|    d< Ǐk	n
+
+ԩSo)sˤ]7o/2sM6ڵkK/jժ?%I+WVPP-o/Y|+I7߿-yڵks)   ̖ٔ(**J׮]S||Ufy4?CaۻD ++K'OjժU,} !::Zaaã`J  I~~~ޯM*[[[խ[D,}   w:            0      L       `@M4C `bӦM    sΪXRSS=  `E Z{ 0\ŋ_mV#  w   nnnŮyf܆  ;   w        	  eĂu[,YDaaaJNNHo/3\#   `< 1Qw[շo_988H_6mڤťD}5     (c5k&OO<}ݺK{꣏>C=T@~}5   G ;5h ]|9ױDEDDcǎڷo$)++K/СCզMrn%IcǎՠA}?r䈞{9=֭^y;w.1^pAw=z?a5K.?V߾}Mź   @yD  Au!eee:6m4<yRY$i۶m7ow3fO>T$_!!!.]Go}:tH?lll4m4׉'4h ]x1x"""t1=òׇ~ŋw^XܾO7SNTMr2F   # @3m4999Y~饗}$f*U̙3sTV-Gr,oذ7o6SN.ٳgjժ?){hڴzEiر9iذa'xBzҥK5dȐӧgA[eի~;?
+  wV  eLڵcyu3?O%e˖ט1ctȑ[aڽ{ZjeKk׮\ukNM6ŋ믿no+ϓ}&     ʘaÆ?yr[nrf͚Ai5jTEt:={FqF]tXKo+Z~)r_({%   PV  \~>}?ܹsFjdkkkc>It
+m#%%EQ}  P 	_V-rLҥKַWwSRRtas&~ҍlBx;    P}GTRR~7o[|˖-ekkիWի̷#Gѣu?^JKKӓO>w<xP׮]ӪUk.2T    P%$$wUN4bjJ?,kԨp?Q۶me˖|ۺ5uT}j׮t颃jܹ9Bl;vO?֭[kƌ9rzY*}   ȍݱ`ZQQQFhhhm6nݺ3CO*Tg4%&&VZ-<<}Tz\,YٳgS*U/R(Voطo|||T~R E
+cS[ rƦoptt,7Ԯ]Hlmm-_W}   ȉ   ;͛=  p#  P{1=cP\xQ>>>     p_   p ;vC    `2 @)cn g!   +*;                         0      L                    0      L                    0      L                    0      L  C͞=[mi{nK    `z衇r8puV6>S^&IZ|/_^"mhBz-n      ZlwIҿoXkX    (#F/߯mjӦM?q℆`iFo,'Nnݺ)$$DO?:d9~C͛7ĉ^
+j+/vvv\]]%I>&Mo#Ghjڴz!޽~      (N8_~E˗/W֭u̙|'׮]w^M4I:tG}zRNcƌѬYuV͟?r_ԉ'4vXVP[yTllbccuԩx7KII?D͞=[z畕U`_    # @%KYZn9(?(>>^T>}Իwo[N&N(;;;M2E(???/gѣի,Iھ}w\o^TV-m߾ktY8Pz4w\>|X    `9KRFFr^%I4$>|8W )zPP$F_t9yxxww}SN)##òǎ9&+++߶+WJRkO||$iΜ9;w$AE    ' Sz$0>|Xjʷ%I!!!ιKbcc-N:%[[[,X>}h޽
+Ypgff
+'nVP[|||YhyI{1tP<xT   @N  UV:~fϞ\ׯ˗sMj*j˖-SڵsצMURE7nT޽uС"#6mǕTQհaCm߾]IIIUbGmڴV\ ~SÆUn   0V  V0~xKWV6m4|G#FQr4` eddhy_֭ƌmjڴi]*((Hϟ;*%%E&MF$M2Ey
+j$hrssرcծ];M0AO.~    aZQQQFhhU`bcc-Zf͚Z~Rϟ?ʕ+)׹/E*UB
+n|occSOj%''FUV-  󈎎VXX   VdccSnnnEnF˞Kʵq_fEUP[%Zj}    2wߕ      ʨmZ{    6          0      L  %%%ҥKyTVVV}0eddX{   (! @uy9s(l٢\Ƿmۦ[nzgOh:qℶmۦ~RfԨQZp-q+g^~u   @	[ Rfu]zUiii2rԩS;v.^('''UZUÇW۶meZJg϶/_0UP!'vZ|{1}'?~mQ*URʕ \-ZG/%Kԭ[7թS疮e׮]Udd؇~&M(00,KΝ;Lծ]C  @@  t9sFiiiPj,233멧/ IGyDG*W,k_ɒk׮6"k׮wފQJ4d={VW\QZgׯkڴi?~!سgr{7J୷ޒ%     ʕ+JOOɿ$}7՘1c,Zh+!!AtKtIUn]nZ~~^3335j(kРA_l7n@M>X_=ǱګV-"3p@h͚5INNɓթS'uYZ\R˗/ջwo]V4ydX">.\qƩ}zG|4!޽{3Ϩ}޽n&''kԩ֭~4HҍU޽{k֭uEj߾};&   yX +WFy;x𠂃s}ݣGIү^۶mSPP:vm۶G}6m$___uAw<~%''+&&FOe˖)))I'O7|LaÆիW>3q*""B-e^5t%yok֬Yjݺj֬̄	H~L81G&z衸88p@󎜝%IgϞA^}xzz*22R_}]-_<ϲ3gTǎӪR-SP+>>^&Lw߭8IҀk.y{{kĈTRmM<Yahҥӛo}    @)KIIQ<訫WX?$$Dw\]]cǎiڵ߿4h W˗/xFqFҥK%Ik׮Uzm6Ojذ<ѪU++$$$2izJII)ڇ&<րT^=իWOЎ;LI+VT
+Tz|=;wu:w4icǎ)111===-&fׯ>UPA~~~$ggg988QիWCm={V;vЀTvmլY<  |` P233=	&96{5zhIRz,mmmeN<f͚Ygdd(+++ڵkV\_~Ynnnꫯ'GGGcڵ9\{ Yqړ
+7ɓ''_\||$QFcAAAsj*8}f~hڴ\0eEDDGѠA4rH988fE_hh_n   NC  wvکN:K[>|X<@Io=zh޼yz'tumذA^?PUTы/gyF~zNÆ&Lk(n{ݛlM47͘1CُU;vs|7z)s=]Ν]v_VZԯ_bSPSNI_{Am%&&F>  ;  e>s-\P~~~j֬{9-^x%''+((HT|wIt.]hѪP6m0I&ڳgzySe5jTcǎY*((HJII˗n:5jԨg[w}/^l4=555GvޭL5o\^^^8T[aaaZr<(I:y|ooo:tHm)00P_~.]h%q    kػwojرcF|||8q¸vZƱcǊwA^~3Ǣ0c͚5cmmڴ1b<y2͛g92ǱzXڻ*7Y	Y KAԡH:qdfA=é[S
+a9a9mOa
+-PA"hXÒ,7!6!\9{y~}N_ڗ-[f?~}̙nX:l?++>ekя~Y\\l)Sg̘avSYYY<x۶o;Ǐϛ7??)St t!0@ʹ~(''ӽѣ3fL{SYY)776Zյrwv+[UU%0:<撒YUYYx  ,233X s#8ϯIzuZ5:kݝ?                &@  V7;   C  1z72z   E   z7z   E  IÇUZZѸ^ii>,o   m >ɓ/d\=,pss%oooW	    >fl6vW%Ð<<<X      .b<==]=    &             &@      	      ` a)    IDAT            &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	  ./kĉ1b8?^IIIm/n^]]6nܨt%%%)99YIII̔$}yyykll/K3q/F:tS}^{=333Sm$?    <\= lΜ9M6iܹzgU\\6e7|:,,̩;.\WhhN<oF믫QHǎӮ]#I:th^|EZt}Q566jժUzuegg;^Ӂ/jƍy/_s=M6kǙ    h >/ͦC캻+11mwTٳھ}F{NaHRSS*dW/??p|M?%I˗/Ν;/kѢEJIIQJJ^y>|X111rssһﾫ6mZ   K >6i$6L_cǎ+s[z뭷8vG?.ݮI&9&7ofĉqI
+UZZ\ӧOԤ۷DGvL>̙!Cg?***ڴg    4 @Ν;5ydjܹڹsg2VU/^~eNQI7Kn_˶~zzg^x;3((H?TRR_|Myg>   ` Nzj=S;.{qՋ$}z'ot蒤XIґ#G۔'?Qtt222:=Ϳ7:uv)oo6   3  1C>dtС蘭#GCǎSQQQ9j(yyyêwlx.\6̱=00Pwq]?/???m۶ݾ    ,mۦ.:|PRRL}}>c/t_˷ߝՋВ%K$IOuE{Zbjkk{|ֿۿA?OUTTg?$?qoog>   ` iŊ~^xAɎ2/_vL%iܸqڱcGƌ~zO>__Zp$)  @iii׽0?.???m޼Y󎤫^Zͻ6>.\~8    M,	C999]WW/*88O9*((nWlllfQddd/otg   \b(33yL3  QbbbkFn{;U   *     	      `             &@      	      `             &@      	      `       j_<]|2JHHȑ#TPEEEmJLLg}01ߗ   ba
++77WNuV)55ձmJLLԞ={z钒l6EDD\WF    b4idFeBCCn}56mRlloq   ̌  p.'d+}YF9surrΟ?{j˖-믿ǏO[ȑ#ꫯtAeggkĉ͛eXd233#8.ah鳨H~|}}I&uǵvt6gE    f"9kؓT^^\̙3%%%lz˖-ԯ~+^mݦ?ڰacR}}iԨQ2e6nܨ^zʴn:]Vz饗}v2Dk֬Qzz֯_/i׎ۙ>nݪ
+ZJڶm[C   `   \oo$}zWꫯj]<y͛!777:tHŊ|}})oooӳ][EEEX,5k,G{-Nٳg+!!A=۫};V=5a8qG!   0P  ؔ)SamذAO?teڼ~etEEEdǶ.]rlk=
+
+RHH?ޫ}:{yy9}   @C  C/~kjڽ{u%IΝsl;{l}תTEE    =zT=zdbbbtԩNjĈT[[+ժݻw+%%EQQQrǎjf￯      Wuuu媫#j3g/^}w>}JJJ`Y,vuЊ+k*..۬ce˖iڱcV\)??^   Uɱw}_͕aR```eUXX(ާ$ǤZeee°Jnnnx'Evn  kY,eff2)yz ٳQMMMe>[g\k~0l7    @  RSSw߭W   0  .q=z         &@      	      `             &@      	      `             &@      	      `  \n    L j믿V^^._aP%$$hȑ
+pfY,(<<\;vlo߿_6lΝ;>Laaa1bD	     U]]ƏTTT(??_tg}V뮻t8q k%%%lpl۳gRSS	    `: jҤI
+a	Upp8T/^ԡCm6pM6)66Vَm˗/w    !  \ɿ$0577;զ$СC>}zڸq9P-[Lr$JrrrT[[hm޼ٱoͲX,2CzG%YF))){ｧkҥJMMua=zT7n\L***҇~(___eggkҤIںu9JTvv&N5k(55U3gc   \_Cg<X<6lؠkתիW+00P[lQVV?ӱoժU/뭷3<Ӧ^YY֭[kתX/cIInݪ
+ZJڶmc5kР͛7?.\q85dYFZ~l6>5JSLƍc    źF{9hҥ:v오>|XvJKKsΩܱo̙0a<<<4l0IRQQ,f͚8)++KRqq߱cS||&L'NH:_,EDDhРA6mZwSjJHHC=*WVpp㬈飳c   . I&)==]/Okt$#PHKKŋKOOoVQQ$)99ٱK4dIRllcyaa$iNZO샂ǏkȑGg   '  e˖iꫯ#IZx/^tN%IΝs,,x6+0TTT_ee***4hРnۿ>   K /rwwWll5n8m۶q6@]]c[nEӟtIIR~~vbcc5b}jݻnÇk׮]Z:qxN;߱cdZe+((lԩS7   0  .nUWWߩ6|r͞=[K,駟vLjyyyyizxb>}ڱ/((H>f͚zJMMM2C+VP~~ϟ竸X˗/wzm%K={֭[3ftZ<==l2-\P;vʕ+'I>}JJJ`Y,   cxW͕aR```eUXX(v
+)W\Q]]BCC;0Z[ܬLg=JOOעET^^ʺ  ?,233X px={V999鰌ta(::2p_gatuޣɿ35\#    (55o7w}"""\=   _   os=   o      &@      	      `             &@      	      `             &@      	      ` I577njv]MMM&    fgZ_+//O/_Lhh4rHt~+Wt{V\t-^2}4bDaܹ:7z	    ba
++77WݶyIUWW;ڿ|α;춍={(55Xf)""±f'   p3"  \P&MRXXLhhu|_y;vL?Oz4˗|_شibccv3     ._PXX{-[tRJ֬YT͜9:99Yϟ޽{e566jÆ:z"""4nܸ.͛eXd233#8.kh飨H~|}}I&i֭:r䈾+<xPٚ8qbq:GJJjjj:<n   `b@ܜ5IYgЪUm۶9-[(22Rկ5k֨A7os]pV^2[Nk׮Uqq^z6}l߾]Cњ5kfӨQ4emܸQә>nq    b]}#e1vX=cׄ	tĉ.O<YSLLJKK׿UYYYРA4mڴNbh֬YS\\t!;M:UgVBBz!UUU)77Wy}    0Xs//nˇ9JT_EEEdǶ.]rlk=
+
+RHH?ޫ}    uaIww%IΝsl;{l}תTEE    Çk׮]Z:qxNjĈT[[+ժݻw+%%EQQQrǎjf￯ 111:u    b\uu`D[d8ٳgkݺu1cFeЊ+k*..۬me˖iڱcV\)???IURRb\w   1L+''Ͳ+0EEE)002*,,nnWܬ*:]Jnnn[x'E#u{   fX< fg*''G555WttvpNpww__W}g   l  QjjM;+}݊p0     M{q   E     0      L                    0      L                    0      L                    0      L                    0      L                    0      L                    0      L                    0      L          nrjnnv0    s `Vv]ZQ}}vcݻyfIRqq.갽wyGGʕ+{<}i۶mڰa~m?ca%0z    "  \AEEEWDDl6~_TTTsj߾}z:l/22R.\PrrrYfQo϶_YYK.j*22R=   kq	 "VUJNNn3rS+I&/qG٧zJ{$iJKKY~WRRVZ'|Rqqq1ceXj*-YDEEEZhQ+99q   3  Z4hPliʔ);5w\wхeUUU%Iڻw߯kٚ0ajkkFTxx|r8qB{v->}Ztm_t\UUU
+7   w3  UhhhW^cǎiƌ裏O>6Mc*::Z,Ю]$I0aј1cڄ		Umm   <  je?X[lQLLz!ܹSׯە|!C[/7l0v0]r[   p"  \Yrp?^ͺ|,̾}T__&1cFq   9 p;裏{U@@TSSRSS#G:ꅅiĉZJJJwܡ?N ˿~~   E7L+''Ǟ=1ct[ɓ_.]RDDD*))QLLunF74n  bQff& g  7#FtɿtKWl   b      L                1L7   `v 葲2z     "*((p0z;     "!!!ÇţZii>,o   пxz Yy{{+!!A:yl6W777)<<\QQQv    \ EP`` &ac   @?F  at0    p     	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	      `             &@      	   $**\'N"`D  xz   N8Yѣ]=˓a4hP%%%*//WBBBy}$iΜ9     ]((('|O$ͦ9i   IDATԩS]:g 0P   Ѕ/R111ɿtlIRSS>#*22Ruel69sFmr֪^3fP~~>s_FY,566N*ljjҾ}T\\.ͦ{*??_Pee>S͜9Sܬ?O{?#ZM}ooNkjj'|BIȑ#hF?^=z>#;PLLu~  |  @***4tN<yR>>>ǏK7(22R<.]n8qBiii1c$)44Ts̙#0t9iܸqլYiNf߯o]KQQ4w\]|Y'OUSS㘤>}Z!!!m&-ckWx9rD%%%4g?kUUUu^lKLLԼytY,͘1C:v옓*  D   @7]t钤WWW*))Q~~\||ᡠ t[gذavWSSTWW';k󊏏BBBqFGG+""BJHHЅdRRR7|4pww)I:sSLLΜ9{Ѳ-&&Fխ*oooʕ+     siȑmOV``"""TSS$D7nnp<NrgL
+tY]]-//$ڱcT__՗@%''$wwwkwww5559^_^     ta̘1*((y4tPkȐ!l9snVEGGY6MummmmPYYY}YV%tu>dٳoߙDYMMM*((Pbbbu     0tPM<Yrr-ݻW[lQhh4mڴ.IQFiϞ=QLLrssCڵkLiiiiڿ|M4H!!!nk޽ؗ}Nwd:s挶o߮z+,,:  qnL+''Ǟa GVpϾo[owu!0:	j~%]=>8dpG\wuWR__/6 wb(33yL3   pR@@@z2i宝Xwfw}|w4y?znnYtu@  {X   tnk銊̧~nM$'  hS_`Z\   `>\ 3      L                    0      L                    0      L                    0      L \bz    'W cƷw]<%y}ϖ>V   <>$5JTφVۛ-R0 Y%t&7j РɸG    psoNmb" \MW3hQW'n>    nNk}@SGK _   Yof[Ըe߲X&  SG_ZC  j=o   j@0g  C  3oIv7]O	   n.>ڿ:յ  gtt? L	̮o   l`*Lp]  jr                                                                                                     d 	Ӥ&    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/shadow.gif b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/shadow.gif
new file mode 100755
index 0000000..026d52a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/shadow.gif
@@ -0,0 +1 @@
+GIF89a   !   ,        ;
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/top.png b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/top.png
new file mode 100755
index 0000000..48749b7
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/top.png
@@ -0,0 +1,7 @@
+PNG
+
+   IHDR     
+   H   gAMA  OX2   tEXtSoftware Adobe ImageReadyqe<  3IDATxn0Ф;Ʋ^!M9I ?jk   K?Z  jÀ}]W   L h?FC  @\ctς^ߥu+   ӚCW؛SP   u{
+6'%.>,'  A?JOV@t
+!,  A -)Lװ|'N+'w  c0X55&:_>e\/e>!aT   rp؛o> gz`$L
+    IENDB`
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.css b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.css
new file mode 100755
index 0000000..5272f1c
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.css
@@ -0,0 +1,864 @@
+body
+{
+	background:#fffff;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+	margin:8px 0 16px;
+	text-align:center;
+}
+
+#form_container
+{
+	background:#fff;
+	border:1px solid #ccc;
+	margin:0 auto;
+	text-align:left;
+	width:640px;
+}
+
+#top
+{
+	display:block;
+	height:10px;
+	margin:10px auto 0;
+	width:650px;
+}
+
+#footer
+{
+	width:640px;
+	clear:both;
+	color:#999999;
+	text-align:center;
+	width:640px;
+	padding-bottom: 15px;
+	font-size: 85%;
+}
+
+#footer a{
+	color:#999999;
+	text-decoration: none;
+	border-bottom: 1px dotted #999999;
+}
+
+#bottom
+{
+	display:block;
+	height:10px;
+	margin:0 auto;
+	width:650px;
+}
+
+form.appnitro
+{
+	margin:20px 20px 0;
+	padding:0 0 20px;
+}
+
+/**** Logo Section  *****/
+h1
+{
+	background-color:#dedede;
+	margin:0;
+	min-height:0;
+	padding:0;
+	text-decoration:none;
+	text-indent:-8000px;
+
+}
+
+h1 a
+{
+
+	display:block;
+	height:100%;
+	min-height:40px;
+	overflow:hidden;
+}
+
+
+img
+{
+	behavior:url(css/iepngfix.htc);
+	border:none;
+}
+
+
+/**** Form Section ****/
+.appnitro
+{
+	font-family:Lucida Grande, Tahoma, Arial, Verdana, sans-serif;
+	font-size:small;
+}
+
+.appnitro li
+{
+	width:61%;
+}
+
+form ul
+{
+	font-size:100%;
+	list-style-type:none;
+	margin:0;
+	padding:0;
+	width:100%;
+}
+
+form li
+{
+	display:block;
+	margin:0;
+	padding:4px 5px 2px 9px;
+	position:relative;
+}
+
+form li:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons:after
+{
+	clear:both;
+	content:".";
+	display:block;
+	height:0;
+	visibility:hidden;
+}
+
+.buttons
+{
+	clear:both;
+	display:block;
+	margin-top:10px;
+}
+
+* html form li
+{
+	height:1%;
+}
+
+* html .buttons
+{
+	height:1%;
+}
+
+* html form li div
+{
+	display:inline-block;
+}
+
+form li div
+{
+	color:#444;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li span
+{
+	color:#444;
+	float:left;
+	margin:0 4px 0 0;
+	padding:0 0 8px;
+}
+
+form li div.left
+{
+	display:inline;
+	float:left;
+	width:48%;
+}
+
+form li div.right
+{
+	display:inline;
+	float:right;
+	width:48%;
+}
+
+form li div.left .medium
+{
+	width:100%;
+}
+
+form li div.right .medium
+{
+	width:100%;
+}
+
+.clear
+{
+	clear:both;
+}
+
+form li div label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li span label
+{
+	clear:both;
+	color:#444;
+	display:block;
+	font-size:9px;
+	line-height:9px;
+	margin:0;
+	padding-top:3px;
+}
+
+form li .datepicker
+{
+	cursor:pointer !important;
+	float:left;
+	height:16px;
+	margin:.1em 5px 0 0;
+	padding:0;
+	width:16px;
+}
+
+.form_description
+{
+	border-bottom:1px dotted #ccc;
+	clear:both;
+	display:inline-block;
+	margin:0 0 1em;
+}
+
+.form_description[class]
+{
+	display:block;
+}
+
+.form_description h2
+{
+	clear:left;
+	font-size:160%;
+	font-weight:400;
+	margin:0 0 3px;
+}
+
+.form_description p
+{
+	font-size:95%;
+	line-height:130%;
+	margin:0 0 12px;
+}
+
+form hr
+{
+	display:none;
+}
+
+form li.section_break
+{
+	border-top:1px dotted #ccc;
+	margin-top:9px;
+	padding-bottom:0;
+	padding-left:9px;
+	padding-top:13px;
+	width:97% !important;
+}
+
+form ul li.first
+{
+	border-top:none !important;
+	margin-top:0 !important;
+	padding-top:0 !important;
+}
+
+form .section_break h3
+{
+	font-size:110%;
+	font-weight:400;
+	line-height:130%;
+	margin:0 0 2px;
+}
+
+form .section_break p
+{
+	font-size:85%;
+
+	margin:0 0 10px;
+}
+
+/**** Buttons ****/
+input.button_text
+{
+	overflow:visible;
+	padding:0 7px;
+	width:auto;
+}
+
+.buttons input
+{
+	font-size:120%;
+	margin-right:5px;
+}
+
+/**** Inputs and Labels ****/
+label.description
+{
+	border:none;
+	color:#222;
+	display:block;
+	font-size:95%;
+	font-weight:700;
+	line-height:150%;
+	padding:0 0 1px;
+}
+
+span.symbol
+{
+	font-size:115%;
+	line-height:130%;
+}
+
+input.text
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+input.file
+{
+	color:#333;
+	font-size:100%;
+	margin:0;
+	padding:2px 0;
+}
+
+textarea.textarea
+{
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+	color:#333;
+	font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+	font-size:100%;
+	margin:0;
+	width:99%;
+}
+
+select.select
+{
+	color:#333;
+	font-size:100%;
+	margin:1px 0;
+	padding:1px 0 0;
+	background:#fff url(shadow.gif) repeat-x top;
+	border-bottom:1px solid #ddd;
+	border-left:1px solid #c3c3c3;
+	border-right:1px solid #c3c3c3;
+	border-top:1px solid #7c7c7c;
+}
+
+
+input.currency
+{
+	text-align:right;
+}
+
+input.checkbox
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+input.radio
+{
+	display:block;
+	height:13px;
+	line-height:1.4em;
+	margin:6px 0 0 3px;
+	width:13px;
+}
+
+label.choice
+{
+	color:#444;
+	display:block;
+	font-size:100%;
+	line-height:1.4em;
+	margin:-1.55em 0 0 25px;
+	padding:4px 0 5px;
+	width:90%;
+}
+
+select.select[class]
+{
+	margin:0;
+	padding:1px 0;
+}
+
+*:first-child+html select.select[class]
+{
+	margin:1px 0;
+}
+
+.safari select.select
+{
+	font-size:120% !important;
+	margin-bottom:1px;
+}
+
+input.small
+{
+	width:25%;
+}
+
+select.small
+{
+	width:25%;
+}
+
+input.medium
+{
+	width:50%;
+}
+
+select.medium
+{
+	width:50%;
+}
+
+input.large
+{
+	width:99%;
+}
+
+select.large
+{
+	width:100%;
+}
+
+textarea.small
+{
+	height:5.5em;
+}
+
+textarea.medium
+{
+	height:10em;
+}
+
+textarea.large
+{
+	height:20em;
+}
+
+/**** Errors ****/
+#error_message
+{
+	background:#fff;
+	border:1px dotted red;
+	margin-bottom:1em;
+	padding-left:0;
+	padding-right:0;
+	padding-top:4px;
+	text-align:center;
+	width:99%;
+}
+
+#error_message_title
+{
+	color:#DF0000;
+	font-size:125%;
+	margin:7px 0 5px;
+	padding:0;
+}
+
+#error_message_desc
+{
+	color:#000;
+	font-size:100%;
+	margin:0 0 .8em;
+}
+
+#error_message_desc strong
+{
+	background-color:#FFDFDF;
+	color:red;
+	padding:2px 3px;
+}
+
+form li.error
+{
+	background-color:#FFDFDF !important;
+	border-bottom:1px solid #EACBCC;
+	border-right:1px solid #EACBCC;
+	margin:3px 0;
+}
+
+form li.error label
+{
+	color:#DF0000 !important;
+}
+
+form p.error
+{
+	clear:both;
+	color:red;
+	font-size:10px;
+	font-weight:700;
+	margin:0 0 5px;
+}
+
+form .required
+{
+	color:red;
+	float:none;
+	font-weight:700;
+}
+
+/**** Guidelines and Error Highlight ****/
+form li.highlighted
+{
+	background-color:#fff7c0;
+}
+
+form .guidelines
+{
+	background:#f5f5f5;
+	border:1px solid #e6e6e6;
+	color:#444;
+	font-size:80%;
+	left:100%;
+	line-height:130%;
+	margin:0 0 0 8px;
+	padding:8px 10px 9px;
+	position:absolute;
+	top:0;
+	visibility:hidden;
+	width:42%;
+	z-index:1000;
+}
+
+form .guidelines small
+{
+	font-size:105%;
+}
+
+form li.highlighted .guidelines
+{
+	visibility:visible;
+}
+
+form li:hover .guidelines
+{
+	visibility:visible;
+}
+
+.no_guidelines .guidelines
+{
+	display:none !important;
+}
+
+.no_guidelines form li
+{
+	width:97%;
+}
+
+.no_guidelines li.section
+{
+	padding-left:9px;
+}
+
+/*** Success Message ****/
+.form_success
+{
+	clear: both;
+	margin: 0;
+	padding: 90px 0pt 100px;
+	text-align: center
+}
+
+.form_success h2 {
+    clear:left;
+    font-size:160%;
+    font-weight:normal;
+    margin:0pt 0pt 3px;
+}
+
+/*** Password ****/
+ul.password{
+    margin-top:60px;
+    margin-bottom: 60px;
+    text-align: center;
+}
+.password h2{
+    color:#DF0000;
+    font-weight:bold;
+    margin:0pt auto 10px;
+}
+
+.password input.text {
+   font-size:170% !important;
+   width:380px;
+   text-align: center;
+}
+.password label{
+   display:block;
+   font-size:120% !important;
+   padding-top:10px;
+   font-weight:bold;
+}
+
+#li_captcha{
+   padding-left: 5px;
+}
+
+
+#li_captcha span{
+	float:none;
+}
+
+/** Embedded Form **/
+
+.embed #form_container{
+	border: none;
+}
+
+.embed #top, .embed #bottom, .embed h1{
+	display: none;
+}
+
+.embed #form_container{
+	width: 100%;
+}
+
+.embed #footer{
+	text-align: left;
+	padding-left: 10px;
+	width: 99%;
+}
+
+.embed #footer.success{
+	text-align: center;
+}
+
+.embed form.appnitro
+{
+	margin:0px 0px 0;
+
+}
+
+
+
+/*** Calendar **********************/
+div.calendar { position: relative; }
+
+.calendar table {
+cursor:pointer;
+border:1px solid #ccc;
+font-size: 11px;
+color: #000;
+background: #fff;
+font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+}
+
+.calendar .button {
+text-align: center;
+padding: 2px;
+}
+
+.calendar .nav {
+background:#f5f5f5;
+}
+
+.calendar thead .title {
+font-weight: bold;
+text-align: center;
+background: #dedede;
+color: #000;
+padding: 2px 0 3px 0;
+}
+
+.calendar thead .headrow {
+background: #f5f5f5;
+color: #444;
+font-weight:bold;
+}
+
+.calendar thead .daynames {
+background: #fff;
+color:#333;
+font-weight:bold;
+}
+
+.calendar thead .name {
+border-bottom: 1px dotted #ccc;
+padding: 2px;
+text-align: center;
+color: #000;
+}
+
+.calendar thead .weekend {
+color: #666;
+}
+
+.calendar thead .hilite {
+background-color: #444;
+color: #fff;
+padding: 1px;
+}
+
+.calendar thead .active {
+background-color: #d12f19;
+color:#fff;
+padding: 2px 0px 0px 2px;
+}
+
+
+.calendar tbody .day {
+width:1.8em;
+color: #222;
+text-align: right;
+padding: 2px 2px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+font-size: 80%;
+color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+color: #fbb;
+}
+
+.calendar table .wn {
+padding: 2px 2px 2px 2px;
+border-right: 1px solid #000;
+background: #666;
+}
+
+.calendar tbody .rowhilite td {
+background: #FFF1AF;
+}
+
+.calendar tbody .rowhilite td.wn {
+background: #FFF1AF;
+}
+
+.calendar tbody td.hilite {
+padding: 1px 1px 1px 1px;
+background:#444 !important;
+color:#fff !important;
+}
+
+.calendar tbody td.active {
+color:#fff;
+background: #529214 !important;
+padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected {
+font-weight: bold;
+border: 1px solid #888;
+padding: 1px 1px 1px 1px;
+background: #f5f5f5 !important;
+color: #222 !important;
+}
+
+.calendar tbody td.weekend {
+color: #666;
+}
+
+.calendar tbody td.today {
+font-weight: bold;
+color: #529214;
+background:#D9EFC2;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell {
+visibility: hidden;
+}
+
+.calendar tbody .emptyrow {
+display: none;
+}
+
+.calendar tfoot .footrow {
+text-align: center;
+background: #556;
+color: #fff;
+}
+
+.calendar tfoot .ttip {
+background: #222;
+color: #fff;
+font-size:10px;
+border-top: 1px solid #dedede;
+padding: 3px;
+}
+
+.calendar tfoot .hilite {
+background: #aaf;
+border: 1px solid #04f;
+color: #000;
+padding: 1px;
+}
+
+.calendar tfoot .active {
+background: #77c;
+padding: 2px 0px 0px 2px;
+}
+
+.calendar .combo {
+position: absolute;
+display: none;
+top: 0px;
+left: 0px;
+width: 4em;
+border: 1px solid #ccc;
+background: #f5f5f5;
+color: #222;
+font-size: 90%;
+z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+text-align: center;
+padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+width: 4em;
+}
+
+.calendar .combo .hilite {
+background: #444;
+color:#fff;
+}
+
+.calendar .combo .active {
+border-top: 1px solid #999;
+border-bottom: 1px solid #999;
+background: #dedede;
+font-weight: bold;
+}
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.js b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.js
new file mode 100755
index 0000000..d3a87e2
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/Server/www/web/test/standard_form/view.js
@@ -0,0 +1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('3(7.X){7["R"+a]=a;7["z"+a]=6(){7["R"+a](7.1k)};7.X("1e",7["z"+a])}E{7.19("z",a,15)}2 j=H V();6 a(){2 e=q.1d("1a");3(e){o(e,"P");2 N=B(q,"*","14");3((e.12<=10)||(N=="")){c(e,"P",d)}}4=B(q,"*","1n");k(i=0;i<4.b;i++){3(4[i].F=="1g"||4[i].F=="1f"||4[i].F=="1c"){4[i].1b=6(){r();c(v.5.5,"f",d)};4[i].O=6(){r();c(v.5.5,"f",d)};j.D(j.b,0,4[i])}E{4[i].O=6(){r();c(v.5.5,"f",d)};4[i].18=6(){o(v.5.5,"f")}}}2 C=17.16.13();2 A=q.M("11");3(C.K("J")+1){c(A[0],"J",d)}3(C.K("I")+1){c(A[0],"I",d)}}6 r(){k(2 i=0;i<j.b;i++){o(j[i].5.5,"f")}}6 B(m,y,w){2 x=(y=="*"&&m.Y)?m.Y:m.M(y);2 G=H V();w=w.1m(/\\-/g,"\\\\-");2 L=H 1l("(^|\\\\s)"+w+"(\\\\s|$)");2 n;k(2 i=0;i<x.b;i++){n=x[i];3(L.1j(n.8)){G.1i(n)}}1h(G)}6 o(p,T){3(p.8){2 h=p.8.Z(" ");2 U=T.t();k(2 i=0;i<h.b;i++){3(h[i].t()==U){h.D(i,1);i--}}p.8=h.S(" ")}}6 c(l,u,Q){3(l.8){2 9=l.8.Z(" ");3(Q){2 W=u.t();k(2 i=0;i<9.b;i++){3(9[i].t()==W){9.D(i,1);i--}}}9[9.b]=u;l.8=9.S(" ")}E{l.8=u}}',62,86,'||var|if|elements|parentNode|function|window|className|_16|initialize|length|addClassName|true|_1|highlighted||_10||el_array|for|_13|_6|_c|removeClassName|_e|document|safari_reset||toUpperCase|_14|this|_8|_9|_7|load|_4|getElementsByClassName|_3|splice|else|type|_a|new|firefox|safari|indexOf|_b|getElementsByTagName|_2|onfocus|no_guidelines|_15|event_load|join|_f|_11|Array|_17|attachEvent|all|split|450|body|offsetWidth|toLowerCase|guidelines|false|userAgent|navigator|onblur|addEventListener|main_body|onclick|file|getElementById|onload|radio|checkbox|return|push|test|event|RegExp|replace|element'.split('|'),0,{}))
\ No newline at end of file
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/bootstrap.php b/vendor/jcalderonzumba/gastonjs/tests/unit/bootstrap.php
new file mode 100644
index 0000000..b39272a
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/bootstrap.php
@@ -0,0 +1,6 @@
+<?php
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Zumba\GastonJS\Tests\Server\LocalWebServer;
+
+LocalWebServer::getInstance("-t www/web/ www/web/index.php");
diff --git a/vendor/jcalderonzumba/gastonjs/tests/unit/commands.txt b/vendor/jcalderonzumba/gastonjs/tests/unit/commands.txt
new file mode 100644
index 0000000..12c1673
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/tests/unit/commands.txt
@@ -0,0 +1,72 @@
+add_extension
+add_header              ---> TESTED
+add_headers             ---> TESTED
+all_html                ---> TESTED
+all_text                ---> TESTED
+attribute               ---> TESTED
+attributes              ---> TESTED
+body                    ---> TESTED
+clear_cookies           ---> TESTED
+clear_network_traffic   ---> TESTED
+click
+click_coordinates
+close_window            ---> TESTED
+cookies                 ---> TESTED
+cookies_enabled         ---> TESTED
+current_url             ---> TESTED
+delete_text
+disabled                ---> TESTED
+double_click
+drag
+equals
+evaluate                ----> TESTED
+execute                 ----> TESTED
+find                    ----> TESTED
+find_within             ----> TESTED
+get_headers             ----> TESTED
+go_back                 ----> TESTED
+go_forward              ----> TESTED
+hover
+mouse_event
+network_traffic         ----> TESTED
+open_new_window         ----> TESTED
+parents                 ----> TESTED
+pop_frame
+push_frame
+reload                  --->  TESTED
+remove_attribute        --->  TESTED
+remove_cookie           --->  TESTED
+render                  --->  TESTED
+render_base64           --->  TESTED
+reset                   --->  TESTED
+resize
+response_headers        --->  TESTED
+right_click
+scroll_to
+select
+select_file             --->  TESTED
+send_keys
+serverRunCommand        --->  TESTED
+serverSendResponse      --->  TESTED
+set
+set_attribute           --->  TESTED
+set_cookie              --->  TESTED
+set_debug
+set_headers             --->  TESTED
+set_http_auth           --->  TESTED
+set_js_errors
+set_paper_size
+set_zoom_factor
+source                  --->  TESTED
+status_code             --->  TESTED
+switch_to_window        --->  TESTED
+tag_name                --->  TESTED
+title                   --->  TESTED
+trigger                 --->  TESTED
+value
+visible                 ---> TESTED
+visible_text            ---> TESTED
+visit                   ---> TESTED
+window_handle           ---> TESTED
+window_handles          ---> TESTED
+window_name             ---> TESTED
diff --git a/vendor/jcalderonzumba/gastonjs/unit_tests.xml b/vendor/jcalderonzumba/gastonjs/unit_tests.xml
new file mode 100644
index 0000000..5473787
--- /dev/null
+++ b/vendor/jcalderonzumba/gastonjs/unit_tests.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="./tests/unit/bootstrap.php"
+         backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="true"
+         syntaxCheck="false">
+    <testsuites>
+        <testsuite>
+            <directory>tests/unit</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+            <exclude>
+                <directory suffix="Trait.php">src/</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml b/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml
new file mode 100644
index 0000000..0dd8e58
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/.travis.yml
@@ -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 {}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md b/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md
new file mode 100644
index 0000000..e7482b2
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/CHANGELOG-0.2.md
@@ -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)
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE b/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE
new file mode 100644
index 0000000..7ba018a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/LICENSE
@@ -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.
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/README.md b/vendor/jcalderonzumba/mink-phantomjs-driver/README.md
new file mode 100644
index 0000000..54a433b
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/README.md
@@ -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>
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh b/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh
new file mode 100755
index 0000000..60bd219
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/bin/run-tests.sh
@@ -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
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json b/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json
new file mode 100644
index 0000000..31b4f57
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/composer.json
@@ -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"
+    }
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml b/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml
new file mode 100644
index 0000000..739fc36
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/integration_tests.xml
@@ -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>
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php
new file mode 100644
index 0000000..d962ff5
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/BasePhantomJSDriver.php
@@ -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);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php
new file mode 100644
index 0000000..327b948
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/CookieTrait.php
@@ -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;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php
new file mode 100644
index 0000000..5a94fc7
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/FormManipulationTrait.php
@@ -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);
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php
new file mode 100644
index 0000000..1d6865a
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/HeadersTrait.php
@@ -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);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php
new file mode 100644
index 0000000..c5b7d63
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/JavascriptTrait.php
@@ -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;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php
new file mode 100644
index 0000000..2b0c96d
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/KeyboardTrait.php
@@ -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);
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php
new file mode 100644
index 0000000..c7cd2eb
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/MouseTrait.php
@@ -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]);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php
new file mode 100644
index 0000000..88ca429
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/NavigationTrait.php
@@ -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();
+  }
+
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php
new file mode 100644
index 0000000..7759a0f
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PageContentTrait.php
@@ -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;
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php
new file mode 100644
index 0000000..ac25170
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/PhantomJSDriver.php
@@ -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;
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig
new file mode 100644
index 0000000..f3372ba
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/check_element.js.twig
@@ -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 %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig
new file mode 100644
index 0000000..791276c
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/execute_script.js.twig
@@ -0,0 +1,3 @@
+{% autoescape false %}
+  {{ script }};
+{% endautoescape %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig
new file mode 100644
index 0000000..8e040f8
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/get_value.js.twig
@@ -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 %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig
new file mode 100644
index 0000000..6b14cad
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_checked.js.twig
@@ -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 %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig
new file mode 100644
index 0000000..a3a18d3
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/is_selected.js.twig
@@ -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 %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig
new file mode 100644
index 0000000..605aec3
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/Resources/Script/set_value.js.twig
@@ -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 %}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php
new file mode 100644
index 0000000..6443dff
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/SessionTrait.php
@@ -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;
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php b/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php
new file mode 100644
index 0000000..92fc6ee
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/src/WindowTrait.php
@@ -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);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHeaderTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHeaderTest.php
new file mode 100644
index 0000000..04f4c51
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHeaderTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Basic;
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+/**
+ * Class HeaderTest
+ * @package Behat\Mink\Tests\Driver\Basic
+ */
+class PhantomJSHeaderTest extends TestCase {
+  /**
+   * test referrer
+   * @group issue130
+   */
+  public function testIssue130() {
+    $this->getSession()->visit($this->pathTo('/issue130.php?p=1'));
+    $page = $this->getSession()->getPage();
+
+    $page->clickLink('Go to 2');
+    $this->assertEquals($this->pathTo('/issue130.php?p=1'), $page->getText());
+  }
+
+  public function testHeaders() {
+    $this->getSession()->setRequestHeader('Accept-Language', 'fr');
+    $this->getSession()->visit($this->pathTo('/headers.php'));
+
+    $this->assertContains('[HTTP_ACCEPT_LANGUAGE] => fr', $this->getSession()->getPage()->getText());
+  }
+
+  public function testSetUserAgent() {
+    $session = $this->getSession();
+
+    $session->setRequestHeader('user-agent', 'foo bar');
+    $session->visit($this->pathTo('/headers.php'));
+    $this->assertContains('[HTTP_USER_AGENT] => foo bar', $session->getPage()->getText());
+  }
+
+  public function testResetHeaders() {
+    $session = $this->getSession();
+
+    $session->setRequestHeader('X-Mink-Test', 'test');
+    $session->visit($this->pathTo('/headers.php'));
+
+    $this->assertContains(
+      '[HTTP_X_MINK_TEST] => test',
+      $session->getPage()->getText(),
+      'The custom header should be sent',
+      true
+    );
+
+    $session->reset();
+    $session->visit($this->pathTo('/headers.php'));
+
+    $this->assertNotContains(
+      '[HTTP_X_MINK_TEST] => test',
+      $session->getPage()->getText(),
+      'The custom header should not be sent after resetting',
+      true
+    );
+  }
+
+  public function testResponseHeaders() {
+    $this->getSession()->visit($this->pathTo('/response_headers.php'));
+
+    $headers = $this->getSession()->getResponseHeaders();
+
+    $lowercasedHeaders = array();
+    foreach ($headers as $name => $value) {
+      $lowercasedHeaders[str_replace('_', '-', strtolower($name))] = $value;
+    }
+
+    $this->assertArrayHasKey('x-mink-test', $lowercasedHeaders);
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHoverTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHoverTest.php
new file mode 100644
index 0000000..2ac8d84
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSHoverTest.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Css;
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+/**
+ * Class PhantomJSHoverTest
+ * @package Behat\Mink\Tests\Driver\Css
+ */
+class PhantomJSHoverTest extends TestCase {
+  /**
+   * @group mouse-events
+   */
+  public function testMouseOverHover() {
+    $this->getSession()->visit($this->pathTo('/css_mouse_events.html'));
+
+    $this->findById('reset-square')->mouseOver();
+    $this->assertActionSquareHeight(100);
+
+    $this->findById('action-square')->mouseOver();
+    $this->assertActionSquareHeight(200);
+  }
+
+  /**
+   * @group mouse-events
+   * @depends testMouseOverHover
+   */
+  public function testClickHover() {
+    $this->getSession()->visit($this->pathTo('/css_mouse_events.html'));
+
+    $this->findById('reset-square')->mouseOver();
+    $this->assertActionSquareHeight(100);
+
+    $this->findById('action-square')->click();
+    $this->assertActionSquareHeight(200);
+  }
+
+  /**
+   * @group mouse-events
+   * @depends testMouseOverHover
+   */
+  public function testDoubleClickHover() {
+    $this->getSession()->visit($this->pathTo('/css_mouse_events.html'));
+
+    $this->findById('reset-square')->mouseOver();
+    $this->assertActionSquareHeight(100);
+
+    $this->findById('action-square')->doubleClick();
+    $this->assertActionSquareHeight(200);
+  }
+
+  /**
+   * @group mouse-events
+   * @depends testMouseOverHover
+   */
+  public function testRightClickHover() {
+    $this->getSession()->visit($this->pathTo('/css_mouse_events.html'));
+
+    $this->findById('reset-square')->mouseOver();
+    $this->assertActionSquareHeight(100);
+
+    $this->findById('action-square')->rightClick();
+    $this->assertActionSquareHeight(200);
+  }
+
+  private function assertActionSquareHeight($expected) {
+    $this->assertEquals(
+      $expected,
+      $this->getSession()->evaluateScript("window.$('#action-square').height();"),
+      'Mouse is located over the object when mouse-related action is performed'
+    );
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSJavascriptEvaluationTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSJavascriptEvaluationTest.php
new file mode 100644
index 0000000..148486e
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSJavascriptEvaluationTest.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Basic;
+
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+/**
+ * Class PhantomJSJavascriptEvaluationTests
+ * @package Behat\Mink\Tests\Driver\Basic
+ */
+class PhantomJSJavascriptEvaluationTest extends TestCase {
+  /**
+   * @dataProvider phantomJSProvideExecutedScript
+   * @param $script
+   */
+  public function testExecuteScript($script) {
+    $this->getSession()->visit($this->pathTo('/index.html'));
+
+    $this->getSession()->executeScript($script);
+
+    sleep(1);
+
+    $heading = $this->getAssertSession()->elementExists('css', 'h1');
+    $this->assertEquals('Hello world', $heading->getText());
+  }
+
+  /**
+   * @return array
+   */
+  public function phantomJSProvideExecutedScript() {
+    return array(
+      array('document.querySelector("h1").textContent = "Hello world"'),
+      array('document.querySelector("h1").textContent = "Hello world";'),
+      array('(function () {document.querySelector("h1").textContent = "Hello world";})()'),
+      array('(function () {document.querySelector("h1").textContent = "Hello world";})();'),
+    );
+  }
+
+  /**
+   * @dataProvider phantomJSProvideEvaluatedScript
+   * @param $script
+   */
+  public function testEvaluateJavascript($script) {
+    $this->getSession()->visit($this->pathTo('/index.html'));
+
+    $this->assertSame(2, $this->getSession()->evaluateScript($script));
+  }
+
+  /**
+   * @return array
+   */
+  public function phantomJSProvideEvaluatedScript() {
+    return array(
+      array('1 + 1'),
+      array('1 + 1;'),
+      array('(function () {return 1+1;})()'),
+      array('(function () {return 1+1;})();'),
+      array('(function () {return function () { return 1+1;}();})();'),
+    );
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSNavigationTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSNavigationTest.php
new file mode 100644
index 0000000..27de68f
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSNavigationTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Basic;
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+class PhantomJSNavigationTest extends TestCase {
+
+  public function testRedirect() {
+    $this->getSession()->visit($this->pathTo('/redirector.php'));
+    $this->assertEquals($this->pathTo('/redirect_destination.html'), $this->getSession()->getCurrentUrl());
+  }
+
+  public function testPageControlls() {
+    $this->getSession()->visit($this->pathTo('/randomizer.php'));
+    $number1 = $this->getSession()->getPage()->find('css', '#number')->getText();
+
+    $this->getSession()->reload();
+    $number2 = $this->getSession()->getPage()->find('css', '#number')->getText();
+
+    $this->assertNotEquals($number1, $number2);
+
+    $this->getSession()->visit($this->pathTo('/links.html'));
+    $this->getSession()->getPage()->clickLink('Random number page');
+
+    $this->assertEquals($this->pathTo('/randomizer.php'), $this->getSession()->getCurrentUrl());
+
+    $this->getSession()->back();
+    $this->assertEquals($this->pathTo('/links.html'), $this->getSession()->getCurrentUrl());
+
+    $this->getSession()->forward();
+    $this->assertEquals($this->pathTo('/randomizer.php'), $this->getSession()->getCurrentUrl());
+  }
+
+  public function testLinks() {
+    $this->getSession()->visit($this->pathTo('/links.html'));
+    $page = $this->getSession()->getPage();
+    $link = $page->findLink('Redirect me to');
+
+    $this->assertNotNull($link);
+    $this->assertRegExp('/redirector\.php$/', $link->getAttribute('href'));
+    $link->click();
+
+    $this->assertEquals($this->pathTo('/redirect_destination.html'), $this->getSession()->getCurrentUrl());
+
+    $this->getSession()->visit($this->pathTo('/links.html'));
+    $page = $this->getSession()->getPage();
+    $link = $page->findLink('basic form image');
+
+    $this->assertNotNull($link);
+    $this->assertRegExp('/basic_form\.html$/', $link->getAttribute('href'));
+    $link->click();
+
+    $this->assertEquals($this->pathTo('/basic_form.html'), $this->getSession()->getCurrentUrl());
+
+    //TODO: Fix or check why this does not work on phantomjs
+    /*$this->getSession()->visit($this->pathTo('/links.html'));
+    $page = $this->getSession()->getPage();
+    $link = $page->findLink("Link with a");
+
+    $this->assertNotNull($link);
+    $this->assertRegExp('/links\.html\?quoted$/', $link->getAttribute('href'));
+    $link->click();
+
+    $this->assertEquals($this->pathTo('/links.html?quoted'), $this->getSession()->getCurrentUrl());*/
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSStatusCodeTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSStatusCodeTest.php
new file mode 100644
index 0000000..e99a583
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSStatusCodeTest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Basic;
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+class PhantomJSStatusCodeTest extends TestCase {
+  public function testStatuses() {
+    $this->getSession()->visit($this->pathTo('/index.html'));
+
+    $this->assertEquals(200, $this->getSession()->getStatusCode());
+    $this->assertEquals($this->pathTo('/index.html'), $this->getSession()->getCurrentUrl());
+
+    $this->getSession()->visit($this->pathTo('/404.php'));
+
+    $this->assertEquals($this->pathTo('/404.php'), $this->getSession()->getCurrentUrl());
+    $this->assertEquals(404, $this->getSession()->getStatusCode());
+    $this->assertEquals('Sorry, page not found', $this->getSession()->getPage()->getText());
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSWindowTest.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSWindowTest.php
new file mode 100644
index 0000000..a7151a1
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/Custom/PhantomJSWindowTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver\Js;
+
+use Behat\Mink\Tests\Driver\TestCase;
+
+/**
+ * Class PhantomJSWindowTest
+ * @package Behat\Mink\Tests\Driver\Js
+ */
+class PhantomJSWindowTest extends TestCase {
+
+  /**
+   *
+   */
+  public function testResizeWindow() {
+    $this->getSession()->visit($this->pathTo('/index.html'));
+    $session = $this->getSession();
+
+    $session->resizeWindow(400, 300);
+    $session->wait(1000, 'false');
+    $javascript = <<<JS
+    (function(){
+        var w = window,
+        d = document,
+        e = d.documentElement,
+        g = d.getElementsByTagName('body')[0],
+        x = w.innerWidth || e.clientWidth || g.clientWidth,
+        y = w.innerHeight|| e.clientHeight|| g.clientHeight;
+        var size = {};
+        size["width"]=x;
+        size["height"]= y;
+        return size;
+    }());
+JS;
+    $pageSize = $session->evaluateScript($javascript);
+    $this->assertEquals(400, $pageSize["width"]);
+    $this->assertEquals(300, $pageSize["height"]);
+  }
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/PhantomJSConfig.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/PhantomJSConfig.php
new file mode 100644
index 0000000..b167af4
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/PhantomJSConfig.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Behat\Mink\Tests\Driver;
+
+use Zumba\Mink\Driver\PhantomJSDriver;
+
+/**
+ * Class PhantomJSConfig
+ * @package Behat\Mink\Tests\Driver
+ */
+class PhantomJSConfig extends AbstractConfig {
+
+  /**
+   * @return PhantomJSConfig
+   */
+  public static function getInstance() {
+    return new self();
+  }
+
+  /**
+   * @return bool
+   */
+  protected function supportsCss() {
+    return true;
+  }
+
+  /**
+   * @return bool
+   */
+  protected function supportsJs() {
+    return true;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createDriver() {
+    $phantomJSHost = $_SERVER["DRIVER_URL"];
+    return new PhantomJSDriver($phantomJSHost, $_SERVER["TEMPLATE_CACHE_DIR"]);
+  }
+
+  /**
+   * @param string $testCase
+   * @param string $test
+   * @return string
+   */
+  public function skipMessage($testCase, $test) {
+    echo "Running $testCase $test\n";
+
+    if ($testCase == "Behat\\Mink\\Tests\\Driver\\Basic\\BasicAuthTest" && $test == "testSetBasicAuth") {
+      //TODO: Fix this error
+      return "TODO: figure out why when sending a bad user is still giving the good login";
+    }
+
+    if ($testCase == "Behat\\Mink\\Tests\\Driver\\Form\\Html5Test" && $test == "testHtml5FormRadioAttribute") {
+      //TODO: Fix this.
+      return "TODO: phantomjs is not giving the element in normal conditions, probably a bug in implementation of getValue";
+    }
+
+    if ($testCase == "Behat\\Mink\\Tests\\Driver\\Js\\JavascriptEvaluationTest" && in_array($test, array("testExecuteScript", "testEvaluateJavascript"))) {
+      return "Due to the nature of the phantomjs javascript implementation we can not use this standard tests";
+    }
+
+    if ($testCase == "Behat\\Mink\\Tests\\Driver\\Js\\WindowTest") {
+      //TODO: This suite is giving random phantomjs crashes, not good for the moment
+      return "This suite is giving random phantomjs crashes, not good for the moment";
+    }
+
+    return parent::skipMessage($testCase, $test);
+  }
+
+}
diff --git a/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/bootstrap.php b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/bootstrap.php
new file mode 100644
index 0000000..7e2d839
--- /dev/null
+++ b/vendor/jcalderonzumba/mink-phantomjs-driver/tests/integration/bootstrap.php
@@ -0,0 +1,3 @@
+<?php
+
+require_once __DIR__ . '/../../vendor/behat/mink/driver-testsuite/bootstrap.php';
