From 7fd1dbbd1c48c31d5ccb14b652cd106902727faa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Marseille?= <marseille.remi@gmail.com>
Date: Fri, 20 Sep 2019 14:52:52 +0200
Subject: [PATCH 01/44] fix(curl): suppress "Transfer-Encoding: chunked" added
 by last version of curl (#96)

---
 lib/WebDriver/Service/CurlService.php | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index 99af82ed..a78c98c0 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -54,6 +54,10 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                     curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
                 } else {
                     $customHeaders[] = 'Content-Length: 0';
+
+                    // Suppress "Transfer-Encoding: chunked" header automatically added by cURL that
+                    // causes a 400 bad request (bad content-length).
+                    $customHeaders[] = 'Transfer-Encoding:';
                 }
 
                 // Suppress "Expect: 100-continue" header automatically added by cURL that
@@ -72,6 +76,10 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                     curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
                 } else {
                     $customHeaders[] = 'Content-Length: 0';
+
+                    // Suppress "Transfer-Encoding: chunked" header automatically added by cURL that
+                    // causes a 400 bad request (bad content-length).
+                    $customHeaders[] = 'Transfer-Encoding:';
                 }
 
                 // Suppress "Expect: 100-continue" header automatically added by cURL that

From bd9405077ca04129a73059a06873bedb5e138402 Mon Sep 17 00:00:00 2001
From: Eloy Lafuente <stronk7@stronk7.com>
Date: Tue, 13 Aug 2019 20:55:31 +0200
Subject: [PATCH 02/44] preg_replace() requires quoting, specially for PHP 7.3
 (#91)

Without this, a number of expressions fail (returning NULL),
leading to both non matches and warning/error:

Warning: preg_replace(): Compilation failed: number too big in {} quantifier at offset 6

This is specially noticeable under PHP 7.3, where the new PCRE2 engine
is more picky and has more features, but also may help some cases with
previous PHP versions, depending of the string being searched/replaced.
---
 lib/WebDriver/Element.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index 09bbc91d..c07c8f68 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -119,6 +119,6 @@ public function getID()
      */
     protected function getElementPath($elementId)
     {
-        return preg_replace(sprintf('/%s$/', $this->id), $elementId, $this->url);
+        return preg_replace(sprintf('/%s$/', preg_quote($this->id)), $elementId, $this->url);
     }
 }

From 7e179d5535a23da214df091805c029ff92bcc26d Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 4 Oct 2017 17:46:17 -0400
Subject: [PATCH 03/44] fixes #80 - support new WebDriver spec element ID

---
 lib/WebDriver/Container.php | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/lib/WebDriver/Container.php b/lib/WebDriver/Container.php
index be1cfa7e..fc698e04 100644
--- a/lib/WebDriver/Container.php
+++ b/lib/WebDriver/Container.php
@@ -32,6 +32,9 @@
  */
 abstract class Container extends AbstractWebDriver
 {
+    const LEGACY_ELEMENT_ID = 'ELEMENT';
+    const WEBDRIVER_ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf';
+
     /**
      * {@inheritdoc}
      */
@@ -203,12 +206,19 @@ public function locate($using, $value)
      */
     protected function webDriverElement($value)
     {
-        return array_key_exists('ELEMENT', (array) $value)
-            ? new Element(
-                $this->getElementPath($value['ELEMENT']), // url
-                $value['ELEMENT'] // id
-            )
-            : null;
+        if (array_key_exists(self::LEGACY_ELEMENT_ID, (array) $value)) {
+            return new Element(
+                $this->getElementPath($value[self::LEGACY_ELEMENT_ID]), // url
+                $value[self::LEGACY_ELEMENT_ID] // id
+            );
+        }
+
+        if (array_key_exists(self::WEBDRIVER_ELEMENT_ID, (array) $value)) {
+            return new Element(
+                $this->getElementPath($value[self::WEBDRIVER_ELEMENT_ID]), // url
+                $value[self::WEBDRIVER_ELEMENT_ID] // id
+            );
+        }
     }
 
     /**

From c850efc2d22667e137c4678887d4c5666d20ddca Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 23 Sep 2019 13:14:33 -0400
Subject: [PATCH 04/44] Refs #91 - tweak the preg_quote() fix

---
 lib/WebDriver/Element.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index c07c8f68..5a0bc229 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -119,6 +119,6 @@ public function getID()
      */
     protected function getElementPath($elementId)
     {
-        return preg_replace(sprintf('/%s$/', preg_quote($this->id)), $elementId, $this->url);
+        return preg_replace('/' . preg_quote($this->id, '/') . '/', $elementId, $this->url);
     }
 }

From 00312b9bef44f2a11f40fe9a5f43b7e6a484f298 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 25 Sep 2019 05:01:23 -0400
Subject: [PATCH 05/44] Refs #91 - restore anchor to regex

---
 lib/WebDriver/Element.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index 5a0bc229..4989437d 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -119,6 +119,6 @@ public function getID()
      */
     protected function getElementPath($elementId)
     {
-        return preg_replace('/' . preg_quote($this->id, '/') . '/', $elementId, $this->url);
+        return preg_replace('/' . preg_quote($this->id, '/') . '$/', $elementId, $this->url);
     }
 }

From b5f330e900e9b3edfc18024a5ec8c07136075712 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Tue, 13 Aug 2019 15:52:06 -0400
Subject: [PATCH 06/44] Update travis CI config

---
 .travis.yml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 941a80a7..f315f0e2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,10 @@ php:
   - 5.6
   - 7.0
   - 7.1
-  - hhvm
+  - 7.2
+  - 7.3
+  - 7.4snapshot
+  - nightly
 
 #before_install:
 #  # This update is mandatory or the 'apt-get install' calls following will fail
@@ -30,3 +33,8 @@ script:
 
 after_script:
   - vendor/bin/coveralls
+
+matrix:
+  allow_failures:
+    - php: 7.4snapshot
+    - php: nightly

From 755497d9be5038d8171be954006a62bcdcd0456e Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 21 Jun 2021 06:23:25 -0400
Subject: [PATCH 07/44] Remove versioneye links from README

---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index ad5ef3a7..79988684 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,6 @@ Distinguishing features:
 
 [![Build Status](https://travis-ci.org/instaclick/php-webdriver.png)](https://travis-ci.org/instaclick/php-webdriver)
 [![Coverage Status](https://coveralls.io/repos/instaclick/php-webdriver/badge.png)](https://coveralls.io/r/instaclick/php-webdriver)
-[![Dependency Status](https://www.versioneye.com/php/instaclick:php-webdriver/badge.png)](https://www.versioneye.com/php/instaclick:php-webdriver/)
 
 [![Latest Stable Version](https://poser.pugx.org/instaclick/php-webdriver/v/stable.png)](https://packagist.org/packages/instaclick/php-webdriver)
 [![Total Downloads](https://poser.pugx.org/instaclick/php-webdriver/downloads.png)](https://packagist.org/packages/instaclick/php-webdriver)

From c81f0543e3da9350a44df65770293a8a79862371 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 21 Jun 2021 06:37:37 -0400
Subject: [PATCH 08/44] Fix phpstan errors

---
 lib/WebDriver/Container.php | 5 +++++
 lib/WebDriver/Storage.php   | 4 +++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/lib/WebDriver/Container.php b/lib/WebDriver/Container.php
index fc698e04..1a98132a 100644
--- a/lib/WebDriver/Container.php
+++ b/lib/WebDriver/Container.php
@@ -35,6 +35,11 @@ abstract class Container extends AbstractWebDriver
     const LEGACY_ELEMENT_ID = 'ELEMENT';
     const WEBDRIVER_ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf';
 
+    /**
+     * @var array
+     */
+    private $strategies;
+
     /**
      * {@inheritdoc}
      */
diff --git a/lib/WebDriver/Storage.php b/lib/WebDriver/Storage.php
index d4586ff9..c0ead1ec 100644
--- a/lib/WebDriver/Storage.php
+++ b/lib/WebDriver/Storage.php
@@ -115,7 +115,9 @@ public function delete()
 
         // delete key from storage
         if (func_num_args() === 1) {
-            return $this->deleteKey(func_get_arg(0));
+            $this->deleteKey(func_get_arg(0));
+
+            return $this;
         }
 
         throw WebDriverException::factory(WebDriverException::UNEXPECTED_PARAMETERS);

From 98306de013f01e972dc6520934b4a99ba4fc20c6 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 28 Jun 2021 14:58:26 -0400
Subject: [PATCH 09/44] fixes #107 - update status/error/ready handling

---
 lib/WebDriver/AbstractWebDriver.php | 23 ++++++++++++++++++++---
 lib/WebDriver/Container.php         |  2 +-
 2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 0d69023c..4564bf81 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -142,7 +142,7 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        if (is_array($result) && !array_key_exists('status', $result)) {
+        if (is_array($result) && !array_key_exists('status', $result) && !isset($result['value']['ready']) && ! isset($result['value']['error'])) {
             throw WebDriverException::factory(
                 WebDriverException::CURL_EXEC,
                 'Payload received from webdriver is valid but unexpected json: ' . substr($rawResult, 0, 1000)
@@ -153,8 +153,25 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         $message = (is_array($value) && array_key_exists('message', $value)) ? $value['message'] : null;
 
         // if not success, throw exception
-        if ((int) $result['status'] !== 0) {
-            throw WebDriverException::factory($result['status'], $message);
+        if (isset($result['status']) && (int) $result['status'] !== 0) {
+            throw WebDriverException::factory(
+                $result['status'],
+                'WebDriver response "status"'
+            );
+        }
+
+        if (isset($value['error'])) {
+            throw WebDriverException::factory(
+                $value['error'],
+                $message ?: 'WebDriver response "error"'
+            );
+        }
+
+        if (isset($value['ready']) && $value['ready'] !== true) {
+            throw WebDriverException::factory(
+                WebDriverException::CURL_EXEC,
+                $message ?: 'WebDriver session not "ready"'
+            );
         }
 
         $sessionId = isset($result['sessionId'])
diff --git a/lib/WebDriver/Container.php b/lib/WebDriver/Container.php
index 1a98132a..4436a7e4 100644
--- a/lib/WebDriver/Container.php
+++ b/lib/WebDriver/Container.php
@@ -43,7 +43,7 @@ abstract class Container extends AbstractWebDriver
     /**
      * {@inheritdoc}
      */
-    public function __construct($url = 'http://localhost:4444/wd/hub')
+    public function __construct($url)
     {
         parent::__construct($url);
 

From 5fdda8f16448755598b40a5451b35725358bf008 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 28 Jun 2021 15:57:39 -0400
Subject: [PATCH 10/44] deprecate ClassLoader

---
 lib/WebDriver/ClassLoader.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/WebDriver/ClassLoader.php b/lib/WebDriver/ClassLoader.php
index 8b1830b1..68bb6069 100644
--- a/lib/WebDriver/ClassLoader.php
+++ b/lib/WebDriver/ClassLoader.php
@@ -25,6 +25,8 @@
  * WebDriver\ClassLoader (autoloader) class
  *
  * @package WebDriver
+ *
+ * @deprecated
  */
 final class ClassLoader
 {

From 26996bcf06b78f5eced18cb5348cb40d4b4fe07e Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 28 Jun 2021 17:50:12 -0400
Subject: [PATCH 11/44] Error handling when webdriver not running

---
 lib/WebDriver/AbstractWebDriver.php                        | 7 +++++++
 lib/WebDriver/Service/CurlService.php                      | 2 ++
 .../WebDriver/{WebDriverTest.php => WebDriverTestBase.php} | 0
 3 files changed, 9 insertions(+)
 rename test/Test/WebDriver/{WebDriverTest.php => WebDriverTestBase.php} (100%)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 4564bf81..aac64dd2 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -124,6 +124,13 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
 
         $httpCode = $info['http_code'];
 
+        if ($httpCode === 0) {
+            throw WebDriverException::factory(
+                WebDriverException::CURL_EXEC,
+                $info['error']
+            );
+        }
+
         // According to https://w3c.github.io/webdriver/webdriver-spec.html all 4xx responses are to be considered
         // an error and return plaintext, while 5xx responses are json encoded
         if ($httpCode >= 400 && $httpCode <= 499) {
diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index a78c98c0..22c51968 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -100,6 +100,8 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
 
         $info = curl_getinfo($curl);
         $info['request_method'] = $requestMethod;
+        $info['errno'] = curl_errno($curl);
+        $info['error'] = curl_error($curl);
 
         if (array_key_exists(CURLOPT_FAILONERROR, $extraOptions) &&
             $extraOptions[CURLOPT_FAILONERROR] &&
diff --git a/test/Test/WebDriver/WebDriverTest.php b/test/Test/WebDriver/WebDriverTestBase.php
similarity index 100%
rename from test/Test/WebDriver/WebDriverTest.php
rename to test/Test/WebDriver/WebDriverTestBase.php

From 961b12178cb71f8667afaf2f66ab3e000e060e1c Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 28 Jun 2021 18:23:05 -0400
Subject: [PATCH 12/44] Exception factory provides a default message

---
 lib/WebDriver/AbstractWebDriver.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index aac64dd2..c6dedb23 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -163,21 +163,21 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         if (isset($result['status']) && (int) $result['status'] !== 0) {
             throw WebDriverException::factory(
                 $result['status'],
-                'WebDriver response "status"'
+                $message
             );
         }
 
         if (isset($value['error'])) {
             throw WebDriverException::factory(
                 $value['error'],
-                $message ?: 'WebDriver response "error"'
+                $message
             );
         }
 
         if (isset($value['ready']) && $value['ready'] !== true) {
             throw WebDriverException::factory(
                 WebDriverException::CURL_EXEC,
-                $message ?: 'WebDriver session not "ready"'
+                $message
             );
         }
 

From 6bc1f44cf23031e68c326cd61e14ec32486f241b Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 13 Oct 2021 23:25:34 -0400
Subject: [PATCH 13/44] Backport some fixes from master branch

---
 lib/WebDriver/AbstractWebDriver.php | 11 +++++++----
 lib/WebDriver/Exception.php         | 13 +++++++------
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index c6dedb23..15fb9f0b 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -149,7 +149,7 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        if (is_array($result) && !array_key_exists('status', $result) && !isset($result['value']['ready']) && ! isset($result['value']['error'])) {
+        if (is_array($result) && ! array_key_exists('status', $result) && ! array_key_exists('value', $result)) {
             throw WebDriverException::factory(
                 WebDriverException::CURL_EXEC,
                 'Payload received from webdriver is valid but unexpected json: ' . substr($rawResult, 0, 1000)
@@ -183,9 +183,12 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
 
         $sessionId = isset($result['sessionId'])
            ? $result['sessionId']
-           : (isset($value['webdriver.remote.sessionid'])
-               ? $value['webdriver.remote.sessionid']
-               : null
+           : (isset($value['sessionId'])
+               ? $value['sessionId']
+               : (isset($value['webdriver.remote.sessionid'])
+                   ? $value['webdriver.remote.sessionid']
+                   : null
+               )
            );
 
         return array(
diff --git a/lib/WebDriver/Exception.php b/lib/WebDriver/Exception.php
index bacb9cb5..e2266f25 100644
--- a/lib/WebDriver/Exception.php
+++ b/lib/WebDriver/Exception.php
@@ -86,6 +86,7 @@ abstract class Exception extends \Exception
     const UNEXPECTED_PARAMETERS = -5;
     const INVALID_REQUEST = -6;
     const UNKNOWN_LOCATOR_STRATEGY = -7;
+    const W3C_WEBDRIVER_ERROR = -8;
 
     private static $errs = array(
 //      self::SUCCESS => array('Success', 'This should never be thrown!'),
@@ -136,12 +137,8 @@ abstract class Exception extends \Exception
     public static function factory($code, $message = null, $previousException = null)
     {
         // unknown error
-        if (!isset(self::$errs[$code])) {
-            if (trim($message) === '') {
-                $message = 'Unknown Error';
-            }
-
-            return new \Exception($message, $code, $previousException);
+        if (! isset(self::$errs[$code])) {
+            $code = self::UNKNOWN_ERROR;
         }
 
         $errorDefinition = self::$errs[$code];
@@ -150,6 +147,10 @@ public static function factory($code, $message = null, $previousException = null
             $message = $errorDefinition[1];
         }
 
+        if (! is_numeric($code)) {
+            $code = self::W3C_WEBDRIVER_ERROR;
+        }
+
         $className = __CLASS__ . '\\' . $errorDefinition[0];
 
         return new $className($message, $code, $previousException);

From 608e8b20a9072f84cac4f2fddb4420893d69eb83 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Tue, 29 Mar 2022 21:28:33 -0400
Subject: [PATCH 14/44] Backport fixes from master branch

---
 lib/WebDriver/AbstractWebDriver.php   | 18 ++++++++----------
 lib/WebDriver/Service/CurlService.php | 13 +++++++------
 2 files changed, 15 insertions(+), 16 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 15fb9f0b..17d31280 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -157,7 +157,12 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         }
 
         $value   = (is_array($result) && array_key_exists('value', $result)) ? $result['value'] : null;
-        $message = (is_array($value) && array_key_exists('message', $value)) ? $value['message'] : null;
+        $message = (is_array($result) && array_key_exists('message', $result))
+            ? $result['message']
+            : ((is_array($value) && array_key_exists('message', $value)) ? $value['message'] : null);
+        $error   = (is_array($result) && array_key_exists('error', $result))
+            ? $result['error']
+            : ((is_array($value) && array_key_exists('error', $value)) ? $value['error'] : null);
 
         // if not success, throw exception
         if (isset($result['status']) && (int) $result['status'] !== 0) {
@@ -167,16 +172,9 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        if (isset($value['error'])) {
+        if (isset($error)) {
             throw WebDriverException::factory(
-                $value['error'],
-                $message
-            );
-        }
-
-        if (isset($value['ready']) && $value['ready'] !== true) {
-            throw WebDriverException::factory(
-                WebDriverException::CURL_EXEC,
+                $error,
                 $message
             );
         }
diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index 22c51968..499dc499 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -23,7 +23,7 @@
 
 namespace WebDriver\Service;
 
-use WebDriver\Exception as WebDriverException;
+use WebDriver\Exception\CurlExec as CurlExecException;
 
 /**
  * WebDriver\Service\CurlService class
@@ -110,8 +110,7 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
         ) {
             curl_close($curl);
 
-            throw WebDriverException::factory(
-                WebDriverException::CURL_EXEC,
+            $e = new CurlExecException(
                 sprintf(
                     "Curl error thrown for http %s to %s%s\n\n%s",
                     $requestMethod,
@@ -119,10 +118,12 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                     $parameters && is_array($parameters) ? ' with params: ' . json_encode($parameters) : '',
                     $error
                 ),
-                $errno,
-                null,
-                $info
+                $errno
             );
+
+            $e->setCurlInfo($info);
+
+            throw $e;
         }
 
         curl_close($curl);

From fb74db58e55957c1cd6c3683a2f63051b680306e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Thu, 14 Apr 2022 17:02:48 +0200
Subject: [PATCH 15/44] Convert element to object from execute result

---
 lib/WebDriver/Session.php | 41 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 39 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index 11867452..4a5c814d 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -34,8 +34,6 @@
  * @method void forward() Navigates forward in the browser history, if possible.
  * @method void back() Navigates backward in the browser history, if possible.
  * @method void refresh() Refresh the current page.
- * @method mixed execute($jsonScript) Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (synchronous)
- * @method mixed execute_async($jsonScript) Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (asynchronous)
  * @method string screenshot() Take a screenshot of the current page.
  * @method array getCookie() Retrieve all cookies visible to the current page.
  * @method array postCookie($jsonCookie) Set a cookie.
@@ -433,4 +431,43 @@ protected function getElementPath($elementId)
     {
         return sprintf('%s/element/%s', $this->url, $elementId);
     }
+
+    /**
+     * @param mixed $data
+     *
+     * @return mixed
+     */
+    private function webDriverElementMulti($data)
+    {
+        $element = $this->webDriverElement($data);
+        if ($element !== null) {
+            return $element;
+        } elseif (is_array($data)) {
+            foreach ($data as $k => $v) {
+                $data[$k] = $this->webDriverElementMulti($v);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (synchronous)
+     */
+    public function execute($jsonScript)
+    {
+        $result = parent::execute($jsonScript);
+
+        return $this->webDriverElementMulti($result);
+    }
+
+    /**
+     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (asynchronous)
+     */
+    public function execute_async($jsonScript)
+    {
+        $result = parent::execute_async($jsonScript);
+
+        return $this->webDriverElementMulti($result);
+    }
 }

From c632618cf47bf989c1ee3b5d441587a3286c2cc7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Thu, 14 Apr 2022 23:15:52 +0200
Subject: [PATCH 16/44] Add support for wd element object as execute args

---
 lib/WebDriver/Session.php | 46 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 40 insertions(+), 6 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index 4a5c814d..e11cab44 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -432,19 +432,37 @@ protected function getElementPath($elementId)
         return sprintf('%s/element/%s', $this->url, $elementId);
     }
 
+    /**
+     * @param array $args
+     *
+     * @return array
+     */
+    private function prepareScriptArguments(array $args)
+    {
+        foreach ($args as $k => $v) {
+            if ($v instanceof Element) {
+                $args[$k] = [Container::WEBDRIVER_ELEMENT_ID => $v->getID()];
+            } elseif (is_array($v)) {
+                $args[$k] = $this->prepareScriptArguments($v);
+            }
+        }
+
+        return $args;
+    }
+
     /**
      * @param mixed $data
      *
      * @return mixed
      */
-    private function webDriverElementMulti($data)
+    private function webDriverElementRecursive($data)
     {
         $element = $this->webDriverElement($data);
         if ($element !== null) {
             return $element;
         } elseif (is_array($data)) {
             foreach ($data as $k => $v) {
-                $data[$k] = $this->webDriverElementMulti($v);
+                $data[$k] = $this->webDriverElementRecursive($v);
             }
         }
 
@@ -453,21 +471,37 @@ private function webDriverElementMulti($data)
 
     /**
      * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (synchronous)
+     *
+     * @param array{script: string, args: array} $jsonScript
+     *
+     * @return mixed
      */
-    public function execute($jsonScript)
+    public function execute(array $jsonScript)
     {
+        if (isset($jsonScript['args'])) {
+            $jsonScript['args'] = $this->prepareScriptArguments($jsonScript['args']);
+        }
+
         $result = parent::execute($jsonScript);
 
-        return $this->webDriverElementMulti($result);
+        return $this->webDriverElementRecursive($result);
     }
 
     /**
      * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (asynchronous)
+     *
+     * @param array{script: string, args: array} $jsonScript
+     *
+     * @return mixed
      */
-    public function execute_async($jsonScript)
+    public function execute_async(array $jsonScript)
     {
+        if (isset($jsonScript['args'])) {
+            $jsonScript['args'] = $this->prepareScriptArguments($jsonScript['args']);
+        }
+
         $result = parent::execute_async($jsonScript);
 
-        return $this->webDriverElementMulti($result);
+        return $this->webDriverElementRecursive($result);
     }
 }

From 703f90083fdb73d3289702a06a61e47b80b62d5d Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Thu, 14 Apr 2022 19:55:35 -0400
Subject: [PATCH 17/44] phpstan config (up to level 4) + cleanup

---
 lib/WebDriver/AbstractWebDriver.php   | 10 +++++-----
 lib/WebDriver/Container.php           |  2 ++
 lib/WebDriver/Exception/CurlExec.php  | 18 +++++++++---------
 lib/WebDriver/SauceLabs/SauceRest.php |  2 +-
 lib/WebDriver/ServiceFactory.php      |  2 +-
 lib/WebDriver/Timeouts.php            |  2 +-
 lib/WebDriver/WebDriver.php           |  2 +-
 phpstan.neon.dist                     |  4 ++++
 8 files changed, 24 insertions(+), 18 deletions(-)
 create mode 100644 phpstan.neon.dist

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 17d31280..d88cb805 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -90,11 +90,11 @@ public function getURL()
     /**
      * Curl request to webdriver server.
      *
-     * @param string $requestMethod HTTP request method, e.g., 'GET', 'POST', or 'DELETE'
-     * @param string $command       If not defined in methods() this function will throw.
-     * @param array  $parameters    If an array(), they will be posted as JSON parameters
-     *                              If a number or string, "/$params" is appended to url
-     * @param array  $extraOptions  key=>value pairs of curl options to pass to curl_setopt()
+     * @param string               $requestMethod HTTP request method, e.g., 'GET', 'POST', or 'DELETE'
+     * @param string               $command       If not defined in methods() this function will throw.
+     * @param array|integer|string $parameters    If an array(), they will be posted as JSON parameters
+     *                                            If a number or string, "/$params" is appended to url
+     * @param array                $extraOptions  key=>value pairs of curl options to pass to curl_setopt()
      *
      * @return array array('value' => ..., 'info' => ...)
      *
diff --git a/lib/WebDriver/Container.php b/lib/WebDriver/Container.php
index 4436a7e4..c964061f 100644
--- a/lib/WebDriver/Container.php
+++ b/lib/WebDriver/Container.php
@@ -224,6 +224,8 @@ protected function webDriverElement($value)
                 $value[self::WEBDRIVER_ELEMENT_ID] // id
             );
         }
+
+        return null;
     }
 
     /**
diff --git a/lib/WebDriver/Exception/CurlExec.php b/lib/WebDriver/Exception/CurlExec.php
index 29c15cbe..a2e636b5 100644
--- a/lib/WebDriver/Exception/CurlExec.php
+++ b/lib/WebDriver/Exception/CurlExec.php
@@ -38,22 +38,22 @@ final class CurlExec extends BaseException
     private $curlInfo = array();
 
     /**
-     * {@inheritdoc}
+     * Get curl info
+     *
+     * @return array
      */
-    public function __construct($message = null, $code = 0, \Exception $previous = null, $curlInfo = array())
+    public function getCurlInfo()
     {
-        parent::__construct($message, $code, $previous);
-
-        $this->curlInfo = $curlInfo;
+        return $this->curlInfo;
     }
 
     /**
-     * Get curl info
+     * Set curl info
      *
-     * @return array
+     * @param array $curlInfo
      */
-    public function getCurlInfo()
+    public function setCurlInfo($curlInfo)
     {
-        return $this->curlInfo;
+        $this->curlInfo = $curlInfo;
     }
 }
diff --git a/lib/WebDriver/SauceLabs/SauceRest.php b/lib/WebDriver/SauceLabs/SauceRest.php
index 7bec171c..3953b0ea 100644
--- a/lib/WebDriver/SauceLabs/SauceRest.php
+++ b/lib/WebDriver/SauceLabs/SauceRest.php
@@ -292,7 +292,7 @@ public function getStatus()
      *
      * @return array
      */
-    public function getBrowsers($termination = false)
+    public function getBrowsers($termination = '')
     {
         if ($termination) {
             return $this->execute('GET', 'info/browsers/' . $termination);
diff --git a/lib/WebDriver/ServiceFactory.php b/lib/WebDriver/ServiceFactory.php
index bf03708c..0c078d73 100755
--- a/lib/WebDriver/ServiceFactory.php
+++ b/lib/WebDriver/ServiceFactory.php
@@ -66,7 +66,7 @@ private function __construct()
      */
     public static function getInstance()
     {
-        if (!self::$instance) {
+        if (self::$instance === null) {
             self::$instance = new self;
         }
 
diff --git a/lib/WebDriver/Timeouts.php b/lib/WebDriver/Timeouts.php
index 4ce72515..2c5a9598 100644
--- a/lib/WebDriver/Timeouts.php
+++ b/lib/WebDriver/Timeouts.php
@@ -47,7 +47,7 @@ protected function methods()
     /**
      * helper method to wait until user-defined condition is met
      *
-     * @param function $callback      callback which returns non-false result if wait condition was met
+     * @param callable $callback      callback which returns non-false result if wait condition was met
      * @param integer  $maxIterations maximum number of iterations
      * @param integer  $sleep         sleep duration in seconds between iterations
      * @param array    $args          optional args; if the callback needs $this, then pass it here
diff --git a/lib/WebDriver/WebDriver.php b/lib/WebDriver/WebDriver.php
index 0c34dcf2..b3cd9b5b 100644
--- a/lib/WebDriver/WebDriver.php
+++ b/lib/WebDriver/WebDriver.php
@@ -27,7 +27,7 @@
  *
  * @package WebDriver
  *
- * @method status
+ * @method array status() Returns information about whether a remote end is in a state in which it can create new sessions.
  */
 class WebDriver extends AbstractWebDriver implements WebDriverInterface
 {
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 00000000..1c57e1fe
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,4 @@
+parameters:
+    level: 4
+    paths:
+        - lib

From 459a42a771cd507e42694804c693c42ad68bbb29 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Thu, 14 Apr 2022 20:01:10 -0400
Subject: [PATCH 18/44] README housekeeping

---
 .coveralls.yml |  1 -
 .gitignore     |  1 +
 .travis.yml    | 40 ----------------------------------------
 README.md      |  3 ---
 4 files changed, 1 insertion(+), 44 deletions(-)
 delete mode 100644 .coveralls.yml
 delete mode 100644 .travis.yml

diff --git a/.coveralls.yml b/.coveralls.yml
deleted file mode 100644
index 6b74c218..00000000
--- a/.coveralls.yml
+++ /dev/null
@@ -1 +0,0 @@
-src_dir: lib
diff --git a/.gitignore b/.gitignore
index fbd8148c..2f7eee69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@ logs/
 nbproject/
 vendor/
 .idea/
+.phpunit.result.cache
 composer.lock
 composer.phar
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f315f0e2..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-dist: trusty
-
-language: php
-
-php:
-  # - 5.3
-  - 5.4
-  - 5.5
-  - 5.6
-  - 7.0
-  - 7.1
-  - 7.2
-  - 7.3
-  - 7.4snapshot
-  - nightly
-
-#before_install:
-#  # This update is mandatory or the 'apt-get install' calls following will fail
-#  - sudo apt-get update -qq
-#  - sudo apt-get install -y apache2 libapache2-mod-fastcgi
-#  # start the xvfb display needed for firefox
-#  - export DISPLAY=:99.0
-#  - sh -e /etc/init.d/xvfb start
-#  - sh ./test/CI/Travis/setup_selenium.sh
-#  - sh ./test/CI/Travis/setup_apache.sh
-
-before_script:
-  - composer install --no-interaction
-
-script:
-  - mkdir -p build/logs
-  - vendor/bin/phpunit --coverage-clover build/logs/clover.xml --group Unit
-
-after_script:
-  - vendor/bin/coveralls
-
-matrix:
-  allow_failures:
-    - php: 7.4snapshot
-    - php: nightly
diff --git a/README.md b/README.md
index 79988684..b534d005 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,6 @@ Distinguishing features:
 * Coding style follows PSR-1, PSR-2, and Symfony2 conventions.
 * Auto-generate API documentation via [phpDocumentor 2.x](http://phpdoc.org/).
 
-[![Build Status](https://travis-ci.org/instaclick/php-webdriver.png)](https://travis-ci.org/instaclick/php-webdriver)
-[![Coverage Status](https://coveralls.io/repos/instaclick/php-webdriver/badge.png)](https://coveralls.io/r/instaclick/php-webdriver)
-
 [![Latest Stable Version](https://poser.pugx.org/instaclick/php-webdriver/v/stable.png)](https://packagist.org/packages/instaclick/php-webdriver)
 [![Total Downloads](https://poser.pugx.org/instaclick/php-webdriver/downloads.png)](https://packagist.org/packages/instaclick/php-webdriver)
 

From 8bb204d82c323fe1ccd308861045422eaa315a07 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Thu, 14 Apr 2022 20:03:17 -0400
Subject: [PATCH 19/44] Coding style + fix to PR #11

---
 lib/WebDriver/Session.php | 99 ++++++++++++++++++++++-----------------
 1 file changed, 56 insertions(+), 43 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index e11cab44..497c10b8 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -425,83 +425,96 @@ public function log()
     }
 
     /**
-     * {@inheritdoc}
+     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (synchronous)
+     *
+     * @param array{script: string, args: array} $jsonScript
+     *
+     * @return mixed
      */
-    protected function getElementPath($elementId)
+    public function execute(array $jsonScript)
     {
-        return sprintf('%s/element/%s', $this->url, $elementId);
+        if (isset($jsonScript['args'])) {
+            $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
+        }
+
+        $result = $this->curl('POST', '/execute', $jsonScript);
+
+        return $this->unserializeResult($result);
     }
 
     /**
-     * @param array $args
+     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (asynchronous)
      *
-     * @return array
+     * @param array{script: string, args: array} $jsonScript
+     *
+     * @return mixed
      */
-    private function prepareScriptArguments(array $args)
+    public function execute_async(array $jsonScript)
     {
-        foreach ($args as $k => $v) {
-            if ($v instanceof Element) {
-                $args[$k] = [Container::WEBDRIVER_ELEMENT_ID => $v->getID()];
-            } elseif (is_array($v)) {
-                $args[$k] = $this->prepareScriptArguments($v);
-            }
+        if (isset($jsonScript['args'])) {
+            $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
         }
 
-        return $args;
+        $result = $this->curl('POST', '/execute_async', $jsonScript);
+
+        return $this->unserializeResult($result);
     }
 
     /**
-     * @param mixed $data
-     *
-     * @return mixed
+     * {@inheritdoc}
      */
-    private function webDriverElementRecursive($data)
+    protected function getElementPath($elementId)
     {
-        $element = $this->webDriverElement($data);
-        if ($element !== null) {
-            return $element;
-        } elseif (is_array($data)) {
-            foreach ($data as $k => $v) {
-                $data[$k] = $this->webDriverElementRecursive($v);
-            }
-        }
-
-        return $data;
+        return sprintf('%s/element/%s', $this->url, $elementId);
     }
 
     /**
-     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (synchronous)
+     * Serialize script arguments (containing web elements)
      *
-     * @param array{script: string, args: array} $jsonScript
+     * @see https://w3c.github.io/webdriver/#executing-script
      *
-     * @return mixed
+     * @param array $arguments
+     *
+     * @return array
      */
-    public function execute(array $jsonScript)
+    private function serializeArguments(array $arguments)
     {
-        if (isset($jsonScript['args'])) {
-            $jsonScript['args'] = $this->prepareScriptArguments($jsonScript['args']);
-        }
+        foreach ($arguments as $key => $value) {
+            // Potential compat-buster, i.e., W3C-specific
+            if ($value instanceof Element) {
+                $arguments[$key] = [Container::WEBDRIVER_ELEMENT_ID => $value->getID()];
+                continue;
+            }
 
-        $result = parent::execute($jsonScript);
+            if (is_array($value)) {
+                $arguments[$key] = $this->serializeArguments($value);
+            }
+        }
 
-        return $this->webDriverElementRecursive($result);
+        return $arguments;
     }
 
     /**
-     * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. (asynchronous)
+     * Unserialize result (containing web elements)
      *
-     * @param array{script: string, args: array} $jsonScript
+     * @param mixed $result
      *
      * @return mixed
      */
-    public function execute_async(array $jsonScript)
+    private function unserializeResult($result)
     {
-        if (isset($jsonScript['args'])) {
-            $jsonScript['args'] = $this->prepareScriptArguments($jsonScript['args']);
+        $element = $this->webDriverElement($result);
+
+        if ($element !== null) {
+            return $element;
         }
 
-        $result = parent::execute_async($jsonScript);
+        if (is_array($result)) {
+            foreach ($result as $key => $value) {
+                $result[$key] = $this->unserializeResult($value);
+            }
+        }
 
-        return $this->webDriverElementRecursive($result);
+        return $result;
     }
 }

From 623eb02be728185110587e4259780e5f7f9f3983 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Fri, 15 Apr 2022 12:18:13 +0200
Subject: [PATCH 20/44] Fix Session::{execute, execute_async}, must use value
 result

---
 lib/WebDriver/Session.php | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index 497c10b8..d4b32680 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -76,8 +76,6 @@ protected function methods()
             'forward' => array('POST'),
             'back' => array('POST'),
             'refresh' => array('POST'),
-            'execute' => array('POST'),
-            'execute_async' => array('POST'),
             'screenshot' => array('GET'),
             'cookie' => array('GET', 'POST'), // for DELETE, use deleteAllCookies()
             'source' => array('GET'),
@@ -433,13 +431,11 @@ public function log()
      */
     public function execute(array $jsonScript)
     {
-        if (isset($jsonScript['args'])) {
-            $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
-        }
+        $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
 
         $result = $this->curl('POST', '/execute', $jsonScript);
 
-        return $this->unserializeResult($result);
+        return $this->unserializeResult($result['value']);
     }
 
     /**
@@ -451,13 +447,11 @@ public function execute(array $jsonScript)
      */
     public function execute_async(array $jsonScript)
     {
-        if (isset($jsonScript['args'])) {
-            $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
-        }
+        $jsonScript['args'] = $this->serializeArguments($jsonScript['args']);
 
         $result = $this->curl('POST', '/execute_async', $jsonScript);
 
-        return $this->unserializeResult($result);
+        return $this->unserializeResult($result['value']);
     }
 
     /**

From b5b18bd2e02b35a14c7085552780588b8876460b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Mon, 18 Apr 2022 00:43:56 +0200
Subject: [PATCH 21/44] Fix compatibility with Selenium 2.x

---
 lib/WebDriver/Session.php | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index d4b32680..f73e379d 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -476,7 +476,13 @@ private function serializeArguments(array $arguments)
         foreach ($arguments as $key => $value) {
             // Potential compat-buster, i.e., W3C-specific
             if ($value instanceof Element) {
-                $arguments[$key] = [Container::WEBDRIVER_ELEMENT_ID => $value->getID()];
+                // preferably we want to detect W3C support and never set nor parse
+                // LEGACY_ELEMENT_ID, until detection is implemented, serialize to both
+                // variants, tested with Selenium v2.53.1 and v3.141.59
+                $arguments[$key] = [
+                    Container::WEBDRIVER_ELEMENT_ID => $value->getID(),
+                    Container::LEGACY_ELEMENT_ID => $value->getID(),
+                ];
                 continue;
             }
 

From c893a53be62e59b2b79aa8b230a8f8b476189c46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Mon, 18 Apr 2022 01:41:08 +0200
Subject: [PATCH 22/44] Assert curl data are always fully serialized before
 request

---
 lib/WebDriver/AbstractWebDriver.php | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index d88cb805..a2aa2e19 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -120,6 +120,8 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             $url .= '/' . $parameters;
         }
 
+        $this->assertNonObjectParameters($parameters);
+
         list($rawResult, $info) = ServiceFactory::getInstance()->getService('service.curl')->execute($requestMethod, $url, $parameters, $extraOptions);
 
         $httpCode = $info['http_code'];
@@ -197,6 +199,32 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         );
     }
 
+    /**
+     * @param mixed $parameters
+     */
+    private function assertNonObjectParameters($parameters)
+    {
+        if ($parameters === null || is_scalar($parameters)) {
+            return;
+        }
+
+        if (is_array($parameters)) {
+            foreach ($parameters as $value) {
+                $this->assertNonObjectParameters($value);
+            }
+
+            return;
+        }
+
+        throw WebDriverException::factory(
+            WebDriverException::UNEXPECTED_PARAMETERS,
+            sprintf(
+                "Unable to serialize non-scalar type %s",
+                is_object($parameters) ? get_class($parameters) : gettype($parameters)
+            )
+        );
+    }
+
     /**
      * Magic method that maps calls to class methods to execute WebDriver commands
      *

From 200b8df772b74d604bebf25ef42ad6f8ee6380a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Mon, 18 Apr 2022 11:43:29 +0200
Subject: [PATCH 23/44] Add testing with mink/driver-testsuite

---
 .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++++++++++++++++
 composer.json            |  6 +++--
 2 files changed, 58 insertions(+), 2 deletions(-)
 create mode 100644 .github/workflows/ci.yml

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..bcc8dd43
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,54 @@
+name: CI
+
+on:
+  push:
+  pull_request:
+
+env:
+  DRIVER_URL: "http://localhost:4444/wd/hub"
+
+jobs:
+  Mink:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        selenium: [ '2.53.1', '3.141.59' ]
+        php: [ '7.2', '7.3', '7.4', '8.0', '8.1' ]
+      fail-fast: false
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: "${{ matrix.php }}"
+          tools: composer
+          ini-values: error_reporting=-1, display_errors=On
+
+      - name: Install dependencies
+        run: |
+          composer config version 1.4.99
+          composer require --no-update behat/mink-selenium2-driver:dev-master --dev --quiet
+          composer require --no-update mink/driver-testsuite:dev-master --dev --quiet
+          php -r '$json = json_decode(file_get_contents ("composer.json"), true); $json["autoload"]["psr-4"]["Behat\\Mink\\Tests\\Driver\\"] = "vendor/behat/mink-selenium2-driver/tests/"; file_put_contents("composer.json", json_encode($json, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT));'
+          composer update --no-interaction --prefer-dist
+
+      - name: Start Selenium & Mink test server
+        run: |
+          mkdir logs
+          docker run --net host --name selenium --volume /dev/shm:/dev/shm --shm-size 2g "selenium/standalone-firefox:${{ matrix.selenium }}" &> logs/selenium.log &
+          vendor/bin/mink-test-server &> logs/mink-test-server.log &
+          while ! nc -z localhost 4444 </dev/null; do echo Waiting for remote driver to start...; sleep 1; done
+          while ! nc -z localhost 8002 </dev/null; do echo Waiting for PHP server to start...; sleep 1; done
+
+      - name: Run Mink tests
+        run: |
+          sed -i "s~vendor/~../../../vendor/~" vendor/behat/mink-selenium2-driver/phpunit.xml.dist
+          # remove test excludes once https://github.com/minkphp/MinkSelenium2Driver/pull/354 Selenium 3 issues are fixed
+          if [ "${{ matrix.selenium }}" = "3.141.59" ]; then
+              vendor/bin/phpunit --exclude-group none --no-coverage -v -c vendor/behat/mink-selenium2-driver --filter '^(?!Behat\\Mink\\Tests\\Driver\\(?:Basic\\IFrameTest::testIFrame|Js\\ChangeEventTest::testSetValueChangeEvent.*|Js\\WindowTest::testWindow|Custom\\TimeoutTest::testInvalidTimeoutSettingThrowsException|Js\\EventsTest::testRightClick)$)'
+          else
+              vendor/bin/phpunit --exclude-group none --no-coverage -v -c vendor/behat/mink-selenium2-driver
+          fi
diff --git a/composer.json b/composer.json
index b0c58550..ff9fb205 100644
--- a/composer.json
+++ b/composer.json
@@ -27,9 +27,11 @@
         "ext-curl": "*"
     },
     "require-dev": {
-        "satooshi/php-coveralls": "^1.0||^2.0",
-        "phpunit/phpunit": "^4.8"
+        "satooshi/php-coveralls": "^1.0 || ^2.0",
+        "phpunit/phpunit": "^8.5 || ^9.5"
     },
+    "minimum-stability": "dev",
+    "prefer-stable": true,
     "autoload": {
         "psr-0": {
             "WebDriver": "lib/"

From 48524f21fd478cd21148220096163f4ceabeb0a1 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 8 Aug 2022 21:07:45 -0400
Subject: [PATCH 24/44] Fixes #118 - partial reverts PR #67 - handling legacy
 webdriver 4xx responses

---
 lib/WebDriver/AbstractWebDriver.php | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index a2aa2e19..1270beb3 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -133,18 +133,17 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        // According to https://w3c.github.io/webdriver/webdriver-spec.html all 4xx responses are to be considered
-        // an error and return plaintext, while 5xx responses are json encoded
-        if ($httpCode >= 400 && $httpCode <= 499) {
-            throw WebDriverException::factory(
-                WebDriverException::CURL_EXEC,
-                'Webdriver http error: ' . $httpCode . ', payload :' . substr($rawResult, 0, 1000)
-            );
-        }
-
         $result = json_decode($rawResult, true);
 
         if (!empty($rawResult) && $result === null && json_last_error() != JSON_ERROR_NONE) {
+            // Legacy webdriver 4xx responses are to be considered // an error and return plaintext
+            if ($httpCode >= 400 && $httpCode <= 499) {
+                throw WebDriverException::factory(
+                    WebDriverException::CURL_EXEC,
+                    'Webdriver http error: ' . $httpCode . ', payload :' . substr($rawResult, 0, 1000)
+                );
+            }
+
             throw WebDriverException::factory(
                 WebDriverException::CURL_EXEC,
                 'Payload received from webdriver is not valid json: ' . substr($rawResult, 0, 1000)

From 6df9fe9961cb9c543f982da19768f4141c96815c Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 8 Aug 2022 21:13:40 -0400
Subject: [PATCH 25/44] Backport exceptions from master branch

---
 lib/WebDriver/Exception.php                   | 78 ++++++++++++++-----
 lib/WebDriver/Exception/CurlExec.php          | 18 +----
 .../Exception/DetachedShadowRoot.php          | 23 ++++++
 .../Exception/ElementClickIntercepted.php     | 23 ++++++
 .../Exception/ElementIsNotSelectable.php      | 17 +---
 .../Exception/ElementNotInteractable.php      | 23 ++++++
 lib/WebDriver/Exception/ElementNotVisible.php | 17 +---
 .../Exception/IMEEngineActivationFailed.php   | 17 +---
 lib/WebDriver/Exception/IMENotAvailable.php   | 17 +---
 .../Exception/InsecureCertificate.php         | 23 ++++++
 lib/WebDriver/Exception/InvalidArgument.php   | 23 ++++++
 .../Exception/InvalidCookieDomain.php         | 17 +---
 .../Exception/InvalidCoordinates.php          | 23 ++++++
 .../Exception/InvalidElementCoordinates.php   | 17 +---
 .../Exception/InvalidElementState.php         | 17 +---
 lib/WebDriver/Exception/InvalidRequest.php    | 17 +---
 lib/WebDriver/Exception/InvalidSelector.php   | 17 +---
 lib/WebDriver/Exception/InvalidSessionID.php  | 23 ++++++
 lib/WebDriver/Exception/JavaScriptError.php   | 17 +---
 .../Exception/JsonParameterExpected.php       | 17 +---
 .../Exception/MoveTargetOutOfBounds.php       | 17 +---
 lib/WebDriver/Exception/NoAlertOpenError.php  | 17 +---
 .../Exception/NoParametersExpected.php        | 17 +---
 lib/WebDriver/Exception/NoSuchAlert.php       | 23 ++++++
 lib/WebDriver/Exception/NoSuchCookie.php      | 23 ++++++
 lib/WebDriver/Exception/NoSuchDriver.php      | 17 +---
 lib/WebDriver/Exception/NoSuchElement.php     | 17 +---
 lib/WebDriver/Exception/NoSuchFrame.php       | 17 +---
 lib/WebDriver/Exception/NoSuchShadowRoot.php  | 23 ++++++
 lib/WebDriver/Exception/NoSuchWindow.php      | 17 +---
 lib/WebDriver/Exception/ObsoleteCommand.php   | 17 +---
 lib/WebDriver/Exception/ScriptTimeout.php     | 17 +---
 lib/WebDriver/Exception/SessionNotCreated.php | 17 +---
 .../Exception/StaleElementReference.php       | 17 +---
 lib/WebDriver/Exception/Timeout.php           | 17 +---
 .../Exception/UnableToCaptureScreen.php       | 23 ++++++
 lib/WebDriver/Exception/UnableToSetCookie.php | 17 +---
 .../Exception/UnexpectedAlertOpen.php         | 17 +---
 .../Exception/UnexpectedParameters.php        | 17 +---
 lib/WebDriver/Exception/UnknownCommand.php    | 17 +---
 lib/WebDriver/Exception/UnknownError.php      | 17 +---
 .../Exception/UnknownLocatorStrategy.php      | 17 +---
 lib/WebDriver/Exception/UnknownMethod.php     | 23 ++++++
 .../Exception/UnsupportedOperation.php        | 23 ++++++
 lib/WebDriver/Exception/XPathLookupError.php  | 17 +---
 45 files changed, 452 insertions(+), 453 deletions(-)
 create mode 100644 lib/WebDriver/Exception/DetachedShadowRoot.php
 create mode 100644 lib/WebDriver/Exception/ElementClickIntercepted.php
 create mode 100644 lib/WebDriver/Exception/ElementNotInteractable.php
 create mode 100644 lib/WebDriver/Exception/InsecureCertificate.php
 create mode 100644 lib/WebDriver/Exception/InvalidArgument.php
 create mode 100644 lib/WebDriver/Exception/InvalidCoordinates.php
 create mode 100644 lib/WebDriver/Exception/InvalidSessionID.php
 create mode 100644 lib/WebDriver/Exception/NoSuchAlert.php
 create mode 100644 lib/WebDriver/Exception/NoSuchCookie.php
 create mode 100644 lib/WebDriver/Exception/NoSuchShadowRoot.php
 create mode 100644 lib/WebDriver/Exception/UnableToCaptureScreen.php
 create mode 100644 lib/WebDriver/Exception/UnknownMethod.php
 create mode 100644 lib/WebDriver/Exception/UnsupportedOperation.php

diff --git a/lib/WebDriver/Exception.php b/lib/WebDriver/Exception.php
index e2266f25..edee0d3a 100644
--- a/lib/WebDriver/Exception.php
+++ b/lib/WebDriver/Exception.php
@@ -1,23 +1,12 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2004 Meta Platforms, Inc.
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
  * @author Justin Bishop <jubishop@gmail.com>
- * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
 namespace WebDriver;
@@ -32,7 +21,7 @@ abstract class Exception extends \Exception
     /**
      * Response status codes
      *
-     * @link http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
+     * @see https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/remote/ErrorCodes.java
      */
     const SUCCESS = 0;
     const NO_SUCH_DRIVER = 6;
@@ -40,10 +29,8 @@ abstract class Exception extends \Exception
     const NO_SUCH_FRAME = 8;
     const UNKNOWN_COMMAND = 9;
     const STALE_ELEMENT_REFERENCE = 10;
-    const ELEMENT_NOT_VISIBLE = 11;
     const INVALID_ELEMENT_STATE = 12;
     const UNKNOWN_ERROR = 13;
-    const ELEMENT_IS_NOT_SELECTABLE = 15;
     const JAVASCRIPT_ERROR = 17;
     const XPATH_LOOKUP_ERROR = 19;
     const TIMEOUT = 21;
@@ -59,6 +46,15 @@ abstract class Exception extends \Exception
     const INVALID_SELECTOR = 32;
     const SESSION_NOT_CREATED = 33;
     const MOVE_TARGET_OUT_OF_BOUNDS = 34;
+    const INVALID_XPATH_SELECTOR = 51;
+    const INVALID_XPATH_SELECTOR_RETURN_TYPER = 52;
+    const ELEMENT_NOT_INTERACTABLE = 60;
+    const INVALID_ARGUMENT = 61;
+    const NO_SUCH_COOKIE = 62;
+    const UNABLE_TO_CAPTURE_SCREEN = 63;
+    const ELEMENT_CLICK_INTERCEPTED = 64;
+    const NO_SUCH_SHADOW_ROOT = 65;
+    const METHOD_NOT_ALLOWED = 405;
 
     // obsolete
     const INDEX_OUT_OF_BOUNDS = 1;
@@ -68,8 +64,10 @@ abstract class Exception extends \Exception
     const NO_STRING_WRAPPER = 5;
     const OBSOLETE_ELEMENT = 10;
     const ELEMENT_NOT_DISPLAYED = 11;
+    const ELEMENT_NOT_VISIBLE = 11;
     const UNHANDLED = 13;
     const EXPECTED = 14;
+    const ELEMENT_IS_NOT_SELECTABLE = 15;
     const ELEMENT_NOT_SELECTABLE = 15;
     const NO_SUCH_DOCUMENT = 16;
     const UNEXPECTED_JAVASCRIPT = 17;
@@ -123,6 +121,50 @@ abstract class Exception extends \Exception
         self::UNEXPECTED_PARAMETERS => array('UnexpectedParameters', 'This command does not expect this number of parameters.'),
         self::INVALID_REQUEST => array('InvalidRequest', 'This command does not support this HTTP request method.'),
         self::UNKNOWN_LOCATOR_STRATEGY => array('UnknownLocatorStrategy', 'This locator strategy is not supported.'),
+        self::INVALID_XPATH_SELECTOR => array('InvalidSelector', 'Argument was an invalid selector.'),
+        self::INVALID_XPATH_SELECTOR_RETURN_TYPER => array('InvalidSelector', 'Argument was an invalid selector.'),
+        self::ELEMENT_NOT_INTERACTABLE => array('ElementNotInteractable', 'A command could not be completed because the element is not pointer- or keyboard interactable.'),
+        self::INVALID_ARGUMENT => array('InvalidArgument', 'The arguments passed to a command are either invalid or malformed.'),
+        self::NO_SUCH_COOKIE => array('NoSuchCookie', 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context\'s active document.'),
+        self::UNABLE_TO_CAPTURE_SCREEN => array('UnableToCaptureScreen', 'A screen capture was made impossible.'),
+        self::ELEMENT_CLICK_INTERCEPTED => array('ElementClickIntercepted', 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.'),
+        self::NO_SUCH_SHADOW_ROOT => array('NoSuchShadowRoot', 'The element does not have a shadow root.'),
+        self::METHOD_NOT_ALLOWED => array('UnsupportedOperation', 'Indicates that a command that should have executed properly cannot be supported for some reason.'),
+
+        // @ss https://w3c.github.io/webdriver/#errors
+        'element not interactable' => array('ElementNotInteractable', 'A command could not be completed because the element is not pointer- or keyboard interactable.'),
+        'element not selectable' => array('ElementIsNotSelectable', 'An attempt was made to select an element that cannot be selected.'),
+        'insecure certificate' => array('InsecureCertificate', 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.'),
+        'invalid argument' => array('InvalidArgument', 'The arguments passed to a command are either invalid or malformed.'),
+        'invalid cookie domain' => array('InvalidCookieDomain', 'An illegal attempt was made to set a cookie under a different domain than the current page.'),
+        'invalid coordinates' => array('InvalidCoordinates', 'The coordinates provided to an interactions operation are invalid.'),
+        'invalid element state' => array('InvalidElementState', 'A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element that isn\'t both editable and resettable.'),
+        'invalid selector' => array('InvalidSelector', 'Argument was an invalid selector.'),
+        'invalid session id' => array('InvalidSessionID', 'Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist or that it\'s not active.'),
+        'javascript error' => array('JavaScriptError', 'An error occurred while executing JavaScript supplied by the user.'),
+        'move target out of bounds' => array('MoveTargetOutOfBounds', 'The target for mouse interaction is not in the browser\'s viewport and cannot be brought into that viewport.'),
+        'no such alert' => array('NoSuchAlert', 'An attempt was made to operate on a modal dialog when one was not open.'),
+        'no such cookie' => array('NoSuchCookie', 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context\'s active document.'),
+        'no such element' => array('NoSuchElement', 'An element could not be located on the page using the given search parameters.'),
+        'no such frame' => array('NoSuchFrame', 'A command to switch to a frame could not be satisfied because the frame could not be found.'),
+        'no such window' => array('NoSuchWindow', 'A command to switch to a window could not be satisfied because the window could not be found.'),
+        'script timeout' => array('ScriptTimeout', 'A script did not complete before its timeout expired.'),
+        'session not created' => array('SessionNotCreated', 'A new session could not be created.'),
+        'stale element reference' => array('StaleElementReference', 'A command failed because the referenced element is no longer attached to the DOM.'),
+        'timeout' => array('Timeout', 'An operation did not complete before its timeout expired.'),
+        'unable to capture screen' => array('UnableToCaptureScreen', 'A screen capture was made impossible.'),
+        'unable to set cookie' => array('UnableToSetCookie', 'A command to set a cookie\'s value could not be satisfied.'),
+        'unexpected alert open' => array('UnexpectedAlertOpen', 'A modal dialog was open, blocking this operation.'),
+        'unknown command' => array('UnknownCommand', 'A command could not be executed because the remote end is not aware of it.'),
+        'unknown error' => array('UnknownError', 'An unknown error occurred in the remote end while processing the command.'),
+        'unknown method' => array('UnknownMethod', 'The requested command matched a known URL but did not match an method for that URL.'),
+        'unsupported operation' => array('UnsupportedOperation', 'Indicates that a command that should have executed properly cannot be supported for some reason.'),
+
+        // obsolete
+        'detached shadow root' => array('DetachedShadowRoot', 'A command failed because the referenced shadow root is no longer attached to the DOM.'),
+        'element click intercepted' => array('ElementClickIntercepted', 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.'),
+        'no such shadow root' => array('NoSuchShadowRoot', 'The element does not have a shadow root.'),
+        'script timeout error' => array('ScriptTimeout', 'A script did not complete before its timeout expired.'),
     );
 
     /**
@@ -143,7 +185,7 @@ public static function factory($code, $message = null, $previousException = null
 
         $errorDefinition = self::$errs[$code];
 
-        if (trim($message) === '') {
+        if ($message === null || trim($message) === '') {
             $message = $errorDefinition[1];
         }
 
diff --git a/lib/WebDriver/Exception/CurlExec.php b/lib/WebDriver/Exception/CurlExec.php
index a2e636b5..bd9a5301 100644
--- a/lib/WebDriver/Exception/CurlExec.php
+++ b/lib/WebDriver/Exception/CurlExec.php
@@ -1,24 +1,12 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
- * @author Gaetano Giunta <giunta.gaetano@gmail.com>
  */
 
 namespace WebDriver\Exception;
diff --git a/lib/WebDriver/Exception/DetachedShadowRoot.php b/lib/WebDriver/Exception/DetachedShadowRoot.php
new file mode 100644
index 00000000..e3375d64
--- /dev/null
+++ b/lib/WebDriver/Exception/DetachedShadowRoot.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\DetachedShadowRoot class
+ *
+ * @package WebDriver
+ */
+final class DetachedShadowRoot extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/ElementClickIntercepted.php b/lib/WebDriver/Exception/ElementClickIntercepted.php
new file mode 100644
index 00000000..8fcac767
--- /dev/null
+++ b/lib/WebDriver/Exception/ElementClickIntercepted.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\ElementClickIntercepted class
+ *
+ * @package WebDriver
+ */
+final class ElementClickIntercepted extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/ElementIsNotSelectable.php b/lib/WebDriver/Exception/ElementIsNotSelectable.php
index 2b6c124c..cadd219d 100644
--- a/lib/WebDriver/Exception/ElementIsNotSelectable.php
+++ b/lib/WebDriver/Exception/ElementIsNotSelectable.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/ElementNotInteractable.php b/lib/WebDriver/Exception/ElementNotInteractable.php
new file mode 100644
index 00000000..bdbb10d5
--- /dev/null
+++ b/lib/WebDriver/Exception/ElementNotInteractable.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\ElementNotInteractable class
+ *
+ * @package WebDriver
+ */
+final class ElementNotInteractable extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/ElementNotVisible.php b/lib/WebDriver/Exception/ElementNotVisible.php
index 80287c01..ca3e64d5 100644
--- a/lib/WebDriver/Exception/ElementNotVisible.php
+++ b/lib/WebDriver/Exception/ElementNotVisible.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/IMEEngineActivationFailed.php b/lib/WebDriver/Exception/IMEEngineActivationFailed.php
index cb2610ff..24f9d7ee 100644
--- a/lib/WebDriver/Exception/IMEEngineActivationFailed.php
+++ b/lib/WebDriver/Exception/IMEEngineActivationFailed.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/IMENotAvailable.php b/lib/WebDriver/Exception/IMENotAvailable.php
index 45fc10fb..4b249b63 100644
--- a/lib/WebDriver/Exception/IMENotAvailable.php
+++ b/lib/WebDriver/Exception/IMENotAvailable.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InsecureCertificate.php b/lib/WebDriver/Exception/InsecureCertificate.php
new file mode 100644
index 00000000..f3d9485d
--- /dev/null
+++ b/lib/WebDriver/Exception/InsecureCertificate.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\InsecureCertificate class
+ *
+ * @package WebDriver
+ */
+final class InsecureCertificate extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/InvalidArgument.php b/lib/WebDriver/Exception/InvalidArgument.php
new file mode 100644
index 00000000..bbe5f62b
--- /dev/null
+++ b/lib/WebDriver/Exception/InvalidArgument.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\InvalidArgument class
+ *
+ * @package WebDriver
+ */
+final class InvalidArgument extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/InvalidCookieDomain.php b/lib/WebDriver/Exception/InvalidCookieDomain.php
index 89ba23dd..76723a88 100644
--- a/lib/WebDriver/Exception/InvalidCookieDomain.php
+++ b/lib/WebDriver/Exception/InvalidCookieDomain.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InvalidCoordinates.php b/lib/WebDriver/Exception/InvalidCoordinates.php
new file mode 100644
index 00000000..a3ae5b2e
--- /dev/null
+++ b/lib/WebDriver/Exception/InvalidCoordinates.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2022 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\InvalidCoordinates class
+ *
+ * @package WebDriver
+ */
+final class InvalidCoordinates extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/InvalidElementCoordinates.php b/lib/WebDriver/Exception/InvalidElementCoordinates.php
index 7a486b0e..2748098a 100644
--- a/lib/WebDriver/Exception/InvalidElementCoordinates.php
+++ b/lib/WebDriver/Exception/InvalidElementCoordinates.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InvalidElementState.php b/lib/WebDriver/Exception/InvalidElementState.php
index ba4f7a72..c7d2f349 100644
--- a/lib/WebDriver/Exception/InvalidElementState.php
+++ b/lib/WebDriver/Exception/InvalidElementState.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InvalidRequest.php b/lib/WebDriver/Exception/InvalidRequest.php
index e18f76c1..63793613 100644
--- a/lib/WebDriver/Exception/InvalidRequest.php
+++ b/lib/WebDriver/Exception/InvalidRequest.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InvalidSelector.php b/lib/WebDriver/Exception/InvalidSelector.php
index 065fc409..7a9674e3 100644
--- a/lib/WebDriver/Exception/InvalidSelector.php
+++ b/lib/WebDriver/Exception/InvalidSelector.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/InvalidSessionID.php b/lib/WebDriver/Exception/InvalidSessionID.php
new file mode 100644
index 00000000..66138eb5
--- /dev/null
+++ b/lib/WebDriver/Exception/InvalidSessionID.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\InvalidSessionID class
+ *
+ * @package WebDriver
+ */
+final class InvalidSessionID extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/JavaScriptError.php b/lib/WebDriver/Exception/JavaScriptError.php
index 202bdceb..753d9c5d 100644
--- a/lib/WebDriver/Exception/JavaScriptError.php
+++ b/lib/WebDriver/Exception/JavaScriptError.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/JsonParameterExpected.php b/lib/WebDriver/Exception/JsonParameterExpected.php
index 1f9b2871..9bb8a0c5 100644
--- a/lib/WebDriver/Exception/JsonParameterExpected.php
+++ b/lib/WebDriver/Exception/JsonParameterExpected.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/MoveTargetOutOfBounds.php b/lib/WebDriver/Exception/MoveTargetOutOfBounds.php
index e6fbbb91..16abca98 100644
--- a/lib/WebDriver/Exception/MoveTargetOutOfBounds.php
+++ b/lib/WebDriver/Exception/MoveTargetOutOfBounds.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoAlertOpenError.php b/lib/WebDriver/Exception/NoAlertOpenError.php
index 9df6db1f..9a2d8a66 100644
--- a/lib/WebDriver/Exception/NoAlertOpenError.php
+++ b/lib/WebDriver/Exception/NoAlertOpenError.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoParametersExpected.php b/lib/WebDriver/Exception/NoParametersExpected.php
index c35c6b43..6fb50da3 100644
--- a/lib/WebDriver/Exception/NoParametersExpected.php
+++ b/lib/WebDriver/Exception/NoParametersExpected.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoSuchAlert.php b/lib/WebDriver/Exception/NoSuchAlert.php
new file mode 100644
index 00000000..6be8627d
--- /dev/null
+++ b/lib/WebDriver/Exception/NoSuchAlert.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\NoSuchAlert class
+ *
+ * @package WebDriver
+ */
+final class NoSuchAlert extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/NoSuchCookie.php b/lib/WebDriver/Exception/NoSuchCookie.php
new file mode 100644
index 00000000..289da28c
--- /dev/null
+++ b/lib/WebDriver/Exception/NoSuchCookie.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\NoSuchCookie class
+ *
+ * @package WebDriver
+ */
+final class NoSuchCookie extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/NoSuchDriver.php b/lib/WebDriver/Exception/NoSuchDriver.php
index 0f177da3..8b8a5ff9 100644
--- a/lib/WebDriver/Exception/NoSuchDriver.php
+++ b/lib/WebDriver/Exception/NoSuchDriver.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoSuchElement.php b/lib/WebDriver/Exception/NoSuchElement.php
index 83e957c9..80d5468f 100644
--- a/lib/WebDriver/Exception/NoSuchElement.php
+++ b/lib/WebDriver/Exception/NoSuchElement.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoSuchFrame.php b/lib/WebDriver/Exception/NoSuchFrame.php
index 47b1b51b..c8667085 100644
--- a/lib/WebDriver/Exception/NoSuchFrame.php
+++ b/lib/WebDriver/Exception/NoSuchFrame.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/NoSuchShadowRoot.php b/lib/WebDriver/Exception/NoSuchShadowRoot.php
new file mode 100644
index 00000000..56194abc
--- /dev/null
+++ b/lib/WebDriver/Exception/NoSuchShadowRoot.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\NoSuchShadowRoot class
+ *
+ * @package WebDriver
+ */
+final class NoSuchShadowRoot extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/NoSuchWindow.php b/lib/WebDriver/Exception/NoSuchWindow.php
index 3a971a81..8441defd 100644
--- a/lib/WebDriver/Exception/NoSuchWindow.php
+++ b/lib/WebDriver/Exception/NoSuchWindow.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/ObsoleteCommand.php b/lib/WebDriver/Exception/ObsoleteCommand.php
index e841919d..b125f74f 100644
--- a/lib/WebDriver/Exception/ObsoleteCommand.php
+++ b/lib/WebDriver/Exception/ObsoleteCommand.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/ScriptTimeout.php b/lib/WebDriver/Exception/ScriptTimeout.php
index 2c8ba7ea..c309b77f 100644
--- a/lib/WebDriver/Exception/ScriptTimeout.php
+++ b/lib/WebDriver/Exception/ScriptTimeout.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/SessionNotCreated.php b/lib/WebDriver/Exception/SessionNotCreated.php
index d2c5c993..a6032a29 100644
--- a/lib/WebDriver/Exception/SessionNotCreated.php
+++ b/lib/WebDriver/Exception/SessionNotCreated.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/StaleElementReference.php b/lib/WebDriver/Exception/StaleElementReference.php
index 2c6f21aa..7641b912 100644
--- a/lib/WebDriver/Exception/StaleElementReference.php
+++ b/lib/WebDriver/Exception/StaleElementReference.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/Timeout.php b/lib/WebDriver/Exception/Timeout.php
index 7c1b7ad6..3b9067d0 100644
--- a/lib/WebDriver/Exception/Timeout.php
+++ b/lib/WebDriver/Exception/Timeout.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnableToCaptureScreen.php b/lib/WebDriver/Exception/UnableToCaptureScreen.php
new file mode 100644
index 00000000..d8ee847b
--- /dev/null
+++ b/lib/WebDriver/Exception/UnableToCaptureScreen.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\UnableToCaptureScreen class
+ *
+ * @package WebDriver
+ */
+final class UnableToCaptureScreen extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/UnableToSetCookie.php b/lib/WebDriver/Exception/UnableToSetCookie.php
index 7fc6b111..e0ebaeec 100644
--- a/lib/WebDriver/Exception/UnableToSetCookie.php
+++ b/lib/WebDriver/Exception/UnableToSetCookie.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnexpectedAlertOpen.php b/lib/WebDriver/Exception/UnexpectedAlertOpen.php
index 0ad6640e..60dd3dfe 100644
--- a/lib/WebDriver/Exception/UnexpectedAlertOpen.php
+++ b/lib/WebDriver/Exception/UnexpectedAlertOpen.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnexpectedParameters.php b/lib/WebDriver/Exception/UnexpectedParameters.php
index a0c3f4e4..6d3f4c65 100644
--- a/lib/WebDriver/Exception/UnexpectedParameters.php
+++ b/lib/WebDriver/Exception/UnexpectedParameters.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2014 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnknownCommand.php b/lib/WebDriver/Exception/UnknownCommand.php
index e03a7462..f93f384f 100644
--- a/lib/WebDriver/Exception/UnknownCommand.php
+++ b/lib/WebDriver/Exception/UnknownCommand.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnknownError.php b/lib/WebDriver/Exception/UnknownError.php
index 4773fd14..2e32b491 100644
--- a/lib/WebDriver/Exception/UnknownError.php
+++ b/lib/WebDriver/Exception/UnknownError.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnknownLocatorStrategy.php b/lib/WebDriver/Exception/UnknownLocatorStrategy.php
index bd0050ef..ddff5ba9 100644
--- a/lib/WebDriver/Exception/UnknownLocatorStrategy.php
+++ b/lib/WebDriver/Exception/UnknownLocatorStrategy.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 
diff --git a/lib/WebDriver/Exception/UnknownMethod.php b/lib/WebDriver/Exception/UnknownMethod.php
new file mode 100644
index 00000000..2e9f9513
--- /dev/null
+++ b/lib/WebDriver/Exception/UnknownMethod.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\UnknownMethod class
+ *
+ * @package WebDriver
+ */
+final class UnknownMethod extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/UnsupportedOperation.php b/lib/WebDriver/Exception/UnsupportedOperation.php
new file mode 100644
index 00000000..b5518804
--- /dev/null
+++ b/lib/WebDriver/Exception/UnsupportedOperation.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @copyright 2019 Anthon Pang
+ * @license Apache-2.0
+ *
+ * @package WebDriver
+ *
+ * @author Anthon Pang <apang@softwaredevelopment.ca>
+ */
+
+namespace WebDriver\Exception;
+
+use WebDriver\Exception as BaseException;
+
+/**
+ * WebDriver\Exception\UnsupportedOperation class
+ *
+ * @package WebDriver
+ */
+final class UnsupportedOperation extends BaseException
+{
+}
diff --git a/lib/WebDriver/Exception/XPathLookupError.php b/lib/WebDriver/Exception/XPathLookupError.php
index 94110eb1..bf6b2c2e 100644
--- a/lib/WebDriver/Exception/XPathLookupError.php
+++ b/lib/WebDriver/Exception/XPathLookupError.php
@@ -1,22 +1,11 @@
 <?php
+
 /**
- * Copyright 2004-2017 Facebook. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @copyright 2013 Anthon Pang
+ * @license Apache-2.0
  *
  * @package WebDriver
  *
- * @author Justin Bishop <jubishop@gmail.com>
  * @author Anthon Pang <apang@softwaredevelopment.ca>
  */
 

From ed8f7741a0952db42686aae0780a0935138a7cf8 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Tue, 9 Aug 2022 10:24:21 -0400
Subject: [PATCH 26/44] improve readability when extracting from value/result

---
 lib/WebDriver/AbstractWebDriver.php | 95 +++++++++++++++--------------
 1 file changed, 50 insertions(+), 45 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 1270beb3..3dcad8be 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -120,7 +120,7 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             $url .= '/' . $parameters;
         }
 
-        $this->assertNonObjectParameters($parameters);
+        $this->assertSerializable($parameters);
 
         list($rawResult, $info) = ServiceFactory::getInstance()->getService('service.curl')->execute($requestMethod, $url, $parameters, $extraOptions);
 
@@ -135,8 +135,8 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
 
         $result = json_decode($rawResult, true);
 
-        if (!empty($rawResult) && $result === null && json_last_error() != JSON_ERROR_NONE) {
-            // Legacy webdriver 4xx responses are to be considered // an error and return plaintext
+        if (! empty($rawResult) && $result === null && json_last_error() != JSON_ERROR_NONE) {
+            // Legacy webdriver 4xx responses are to be considered a plaintext error
             if ($httpCode >= 400 && $httpCode <= 499) {
                 throw WebDriverException::factory(
                     WebDriverException::CURL_EXEC,
@@ -157,13 +157,9 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        $value   = (is_array($result) && array_key_exists('value', $result)) ? $result['value'] : null;
-        $message = (is_array($result) && array_key_exists('message', $result))
-            ? $result['message']
-            : ((is_array($value) && array_key_exists('message', $value)) ? $value['message'] : null);
-        $error   = (is_array($result) && array_key_exists('error', $result))
-            ? $result['error']
-            : ((is_array($value) && array_key_exists('error', $value)) ? $value['error'] : null);
+        $value   = $this->offsetGet('value', $result);
+        $message = $this->offsetGet('message', $result) ?: $this->offsetGet('message', $value);
+        $error   = $this->offsetGet('error', $result)   ?: $this->offsetGet('error', $value);
 
         // if not success, throw exception
         if (isset($result['status']) && (int) $result['status'] !== 0) {
@@ -180,15 +176,9 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        $sessionId = isset($result['sessionId'])
-           ? $result['sessionId']
-           : (isset($value['sessionId'])
-               ? $value['sessionId']
-               : (isset($value['webdriver.remote.sessionid'])
-                   ? $value['webdriver.remote.sessionid']
-                   : null
-               )
-           );
+        $sessionId = $this->offsetGet('sessionId', $result)
+                  ?: $this->offsetGet('sessionId', $value)
+                  ?: $this->offsetGet('webdriver.remote.sessionid', $value);
 
         return array(
             'value'      => $value,
@@ -198,32 +188,6 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         );
     }
 
-    /**
-     * @param mixed $parameters
-     */
-    private function assertNonObjectParameters($parameters)
-    {
-        if ($parameters === null || is_scalar($parameters)) {
-            return;
-        }
-
-        if (is_array($parameters)) {
-            foreach ($parameters as $value) {
-                $this->assertNonObjectParameters($value);
-            }
-
-            return;
-        }
-
-        throw WebDriverException::factory(
-            WebDriverException::UNEXPECTED_PARAMETERS,
-            sprintf(
-                "Unable to serialize non-scalar type %s",
-                is_object($parameters) ? get_class($parameters) : gettype($parameters)
-            )
-        );
-    }
-
     /**
      * Magic method that maps calls to class methods to execute WebDriver commands
      *
@@ -273,6 +237,47 @@ public function __call($name, $arguments)
         return $result['value'];
     }
 
+    /**
+     * Sanity check
+     *
+     * @param mixed $parameters
+     */
+    private function assertSerializable($parameters)
+    {
+        if ($parameters === null || is_scalar($parameters)) {
+            return;
+        }
+
+        if (is_array($parameters)) {
+            foreach ($parameters as $value) {
+                $this->assertSerializable($value);
+            }
+
+            return;
+        }
+
+        throw WebDriverException::factory(
+            WebDriverException::UNEXPECTED_PARAMETERS,
+            sprintf(
+                "Unable to serialize non-scalar type %s",
+                is_object($parameters) ? get_class($parameters) : gettype($parameters)
+            )
+        );
+    }
+
+    /**
+     * Extract value from result
+     *
+     * @param string $key
+     * @param mixed  $result
+     *
+     * @return string|null
+     */
+    private function offsetGet($key, $result)
+    {
+        return (is_array($result) && array_key_exists($key, $result)) ? $result[$key] : null;
+    }
+
     /**
      * Get default HTTP request method for a given WebDriver command
      *

From a39a1f6dc0f4ddd8b2438fa5eb1f67755730d606 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= <mvorisek@mvorisek.cz>
Date: Sun, 23 Oct 2022 17:40:55 +0200
Subject: [PATCH 27/44] POST /window must define handle first for Selenium 3.x
 and Firefox

---
 lib/WebDriver/Session.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index f73e379d..c6771e2e 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -218,7 +218,7 @@ public function deleteCookie($cookieName)
     /**
      * window methods: /session/:sessionId/window (POST, DELETE)
      * - $session->window() - close current window
-     * - $session->window($name) - set focus
+     * - $session->window($window_handle) - set focus
      * - $session->window($window_handle)->method() - chaining
      *
      * @return \WebDriver\Window|\WebDriver\Session
@@ -233,7 +233,7 @@ public function window()
         }
 
         // set focus
-        $arg = func_get_arg(0); // window handle or name attribute
+        $arg = func_get_arg(0); // window handle
 
         if (is_array($arg)) {
             $this->curl('POST', '/window', $arg);
@@ -260,13 +260,13 @@ public function deleteWindow()
     /**
      * Set focus to window: /session/:sessionId/window (POST)
      *
-     * @param mixed $name window handler or name attribute
+     * @param mixed $name window handle
      *
      * @return \WebDriver\Session
      */
     public function focusWindow($name)
     {
-        $this->curl('POST', '/window', array('name' => $name));
+        $this->curl('POST', '/window', array('handle' => $name, 'name' => $name));
 
         return $this;
     }

From 4a0c79a62d8a81f53427788d99aa930855bf12b0 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 11 Jan 2023 16:03:52 -0500
Subject: [PATCH 28/44] Fix semantic change introduced by ed8f7741

---
 lib/WebDriver/AbstractWebDriver.php | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index 3dcad8be..dd9825f0 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -158,8 +158,16 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
         }
 
         $value   = $this->offsetGet('value', $result);
-        $message = $this->offsetGet('message', $result) ?: $this->offsetGet('message', $value);
-        $error   = $this->offsetGet('error', $result)   ?: $this->offsetGet('error', $value);
+        $message = $this->offsetGet('message', $result);
+        $error   = $this->offsetGet('error', $result);
+
+        if ($message === null) {
+            $message = $this->offsetGet('message', $value);
+        }
+
+        if ($error === null) {
+            $error = $this->offsetGet('error', $value);
+        }
 
         // if not success, throw exception
         if (isset($result['status']) && (int) $result['status'] !== 0) {

From e334e5ebc9d4867f0342d2a98806d74aeccc7ca8 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 11 Jan 2023 16:22:13 -0500
Subject: [PATCH 29/44] tweaking error handling (again)

---
 lib/WebDriver/AbstractWebDriver.php | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/lib/WebDriver/AbstractWebDriver.php b/lib/WebDriver/AbstractWebDriver.php
index dd9825f0..d3b57da4 100644
--- a/lib/WebDriver/AbstractWebDriver.php
+++ b/lib/WebDriver/AbstractWebDriver.php
@@ -157,18 +157,12 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
-        $value   = $this->offsetGet('value', $result);
-        $message = $this->offsetGet('message', $result);
-        $error   = $this->offsetGet('error', $result);
+        $value = $this->offsetGet('value', $result);
 
-        if ($message === null) {
+        if (($message = $this->offsetGet('message', $result)) === null) {
             $message = $this->offsetGet('message', $value);
         }
 
-        if ($error === null) {
-            $error = $this->offsetGet('error', $value);
-        }
-
         // if not success, throw exception
         if (isset($result['status']) && (int) $result['status'] !== 0) {
             throw WebDriverException::factory(
@@ -177,6 +171,10 @@ protected function curl($requestMethod, $command, $parameters = null, $extraOpti
             );
         }
 
+        if (($error = $this->offsetGet('error', $result)) === null) {
+            $error = $this->offsetGet('error', $value);
+        }
+
         if (isset($error)) {
             throw WebDriverException::factory(
                 $error,

From 7b8bf3406aa57a6dfd4559380f463dc8fee22052 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 4 Dec 2023 08:55:00 -0500
Subject: [PATCH 30/44] charset is case-insensitive in Content-Type but we
 downcase here for consistency with our other codebases; at the same time,
 remove charset from Accept header as it isn't widely supported

---
 lib/WebDriver/Service/CurlService.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index 499dc499..1b77197f 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -38,8 +38,8 @@ class CurlService implements CurlServiceInterface
     public function execute($requestMethod, $url, $parameters = null, $extraOptions = array())
     {
         $customHeaders = array(
-            'Content-Type: application/json;charset=UTF-8',
-            'Accept: application/json;charset=UTF-8',
+            'Content-Type: application/json;charset=utf-8',
+            'Accept: application/json',
         );
 
         $curl = curl_init($url);

From 5dd967824b5c86ae79a328111b514f97ab604f49 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 4 Dec 2023 13:32:41 -0500
Subject: [PATCH 31/44] clean-up Browser consts

---
 lib/WebDriver/Browser.php | 41 ++++++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 18 deletions(-)

diff --git a/lib/WebDriver/Browser.php b/lib/WebDriver/Browser.php
index d23c3089..3da4da35 100644
--- a/lib/WebDriver/Browser.php
+++ b/lib/WebDriver/Browser.php
@@ -30,23 +30,28 @@
 final class Browser
 {
     /**
-     * Check browser names used in static functions in the selenium source:
-     * @see http://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/remote/DesiredCapabilities.java
-     *
-     * Note: Capability array takes these browserNames and not the "browserTypes"
-     *
-     * Also check
-     * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Capabilities_JSON_Object
+     * @see https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/remote/Browser.java
      */
-    const ANDROID           = 'android';
-    const CHROME            = 'chrome';
-    const FIREFOX           = 'firefox';
-    const HTMLUNIT          = 'htmlunit';
-    const IE                = 'internet explorer';
-    const INTERNET_EXPLORER = 'internet explorer';
-    const IPHONE            = 'iPhone';
-    const IPAD              = 'iPad';
-    const OPERA             = 'opera';
-    const PHANTOMJS         = 'phantomjs';
-    const SAFARI            = 'safari';
+    const CHROME              = 'chrome';
+    const EDGE                = 'MicrosoftEdge';
+    const FIREFOX             = 'firefox';
+    const HTMLUNIT            = 'htmlunit';
+    const IE                  = 'internet explorer';
+    const OPERA               = 'opera';
+    const SAFARI              = 'safari';
+    const SAFARI_TECH_PREVIEW = 'Safari Technology Preview';
+
+    /**
+     * @see https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/webdriver/capabilities.js
+     * @deprecated
+     */
+    const ANDROID             = 'android';
+    const EDGEHTML            = 'EdgeHTML';
+    const INTERNET_EXPLORER   = 'internet explorer';
+    const IPAD                = 'iPad';
+    const IPHONE              = 'iPhone';
+    const MSEDGE              = 'MicrosoftEdge';
+    const OPERA_BLINK         = 'operablink';
+    const PHANTOM_JS          = 'phantomjs';
+    const PHANTOMJS           = 'phantomjs';
 }

From fff9a8ed9c874face17e541c73e070f0c0571683 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 4 Dec 2023 15:07:40 -0500
Subject: [PATCH 32/44] Element::css() works differently

---
 lib/WebDriver/Element.php | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index 4989437d..0ec21bd5 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -42,7 +42,6 @@
  * @method array location() Determine an element's location on the page.
  * @method array location_in_view() Determine an element's location on the screen once it has been scrolled into view.
  * @method array size() Determine an element's size in pixels.
- * @method string css($propertyName) Query the value of an element's computed CSS property.
  */
 final class Element extends Container
 {
@@ -66,7 +65,6 @@ protected function methods()
             'location' => array('GET'),
             'location_in_view' => array('GET'),
             'size' => array('GET'),
-            'css' => array('GET'),
         );
     }
 
@@ -114,6 +112,20 @@ public function getID()
         return $this->id;
     }
 
+    /**
+     * Query the value of an element’s computed CSS property: /session/:sessionId/element/:id/css/:propertyName
+     *
+     * @param string $propertyName
+     *
+     * @return mixed
+     */
+    public function css($propertyName)
+    {
+        $result = $this->curl('GET', "/css/$propertyName");
+
+        return $result['value'];
+    }
+
     /**
      * {@inheritdoc}
      */

From 520d22b654c04b3d58136e1c966ffac42cead685 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 4 Dec 2023 16:17:26 -0500
Subject: [PATCH 33/44] fixes #122 - translate empty params in POST

---
 lib/WebDriver/Service/CurlService.php | 24 ++++++++----------------
 1 file changed, 8 insertions(+), 16 deletions(-)

diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index 1b77197f..dbae4ecd 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -50,16 +50,12 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                 break;
 
             case 'POST':
-                if ($parameters && is_array($parameters)) {
-                    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
-                } else {
-                    $customHeaders[] = 'Content-Length: 0';
-
-                    // Suppress "Transfer-Encoding: chunked" header automatically added by cURL that
-                    // causes a 400 bad request (bad content-length).
-                    $customHeaders[] = 'Transfer-Encoding:';
+                if ( ! $parameters || ! is_array($parameters)) {
+                    $parameters = array();
                 }
 
+                curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
+
                 // Suppress "Expect: 100-continue" header automatically added by cURL that
                 // causes a 1 second delay if the remote server does not support Expect.
                 $customHeaders[] = 'Expect:';
@@ -72,16 +68,12 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                 break;
 
             case 'PUT':
-                if ($parameters && is_array($parameters)) {
-                    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
-                } else {
-                    $customHeaders[] = 'Content-Length: 0';
-
-                    // Suppress "Transfer-Encoding: chunked" header automatically added by cURL that
-                    // causes a 400 bad request (bad content-length).
-                    $customHeaders[] = 'Transfer-Encoding:';
+                if ( ! $parameters || ! is_array($parameters)) {
+                    $parameters = array();
                 }
 
+                curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
+
                 // Suppress "Expect: 100-continue" header automatically added by cURL that
                 // causes a 1 second delay if the remote server does not support Expect.
                 $customHeaders[] = 'Expect:';

From 4ac07095d7f99714c6d47b4cd5f7a9cd2ac9b8a9 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 4 Dec 2023 17:34:17 -0500
Subject: [PATCH 34/44] Element::attribute() works differently

---
 lib/WebDriver/Element.php | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index 0ec21bd5..da15ddf2 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -36,7 +36,6 @@
  * @method void clear() Clear a TEXTAREA or text INPUT element's value.
  * @method boolean selected() Determine if an OPTION element, or an INPUT element of type checkbox or radiobutton is currently selected.
  * @method boolean enabled() Determine if an element is currently enabled.
- * @method string attribute($attributeName) Get the value of an element's attribute.
  * @method boolean equals($otherId) Test if two element IDs refer to the same DOM element.
  * @method boolean displayed() Determine if an element is currently displayed.
  * @method array location() Determine an element's location on the page.
@@ -59,7 +58,6 @@ protected function methods()
             'clear' => array('POST'),
             'selected' => array('GET'),
             'enabled' => array('GET'),
-            'attribute' => array('GET'),
             'equals' => array('GET'),
             'displayed' => array('GET'),
             'location' => array('GET'),
@@ -112,6 +110,20 @@ public function getID()
         return $this->id;
     }
 
+    /**
+     * Get the value of an element's attribute: /session/:sessionId/element/:id/attribute/:name
+     *
+     * @param string name
+     *
+     * @return mixed
+     */
+    public function attribute($name)
+    {
+        $result = $this->curl('GET', "/attribute/$name");
+
+        return $result['value'];
+    }
+
     /**
      * Query the value of an element’s computed CSS property: /session/:sessionId/element/:id/css/:propertyName
      *

From 9e6001886e6c3c9ac2939fa529309f69f08e2500 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Wed, 6 Dec 2023 08:42:44 -0500
Subject: [PATCH 35/44] fix empty POST/PUT body in 520d22b6

---
 lib/WebDriver/Service/CurlService.php | 27 ++++++++-------------------
 1 file changed, 8 insertions(+), 19 deletions(-)

diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index dbae4ecd..4c5fa1f1 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -50,36 +50,25 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                 break;
 
             case 'POST':
-                if ( ! $parameters || ! is_array($parameters)) {
-                    $parameters = array();
-                }
+            case 'PUT':
+                $parameters =  ! $parameters || ! is_array($parameters)
+                    ? '{}'
+                    : json_encode($parameters);
 
-                curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
+                curl_setopt($curl, CURLOPT_POSTFIELDS, $parameters);
 
                 // Suppress "Expect: 100-continue" header automatically added by cURL that
                 // causes a 1 second delay if the remote server does not support Expect.
                 $customHeaders[] = 'Expect:';
 
-                curl_setopt($curl, CURLOPT_POST, true);
+                $requestMethod === 'POST'
+                    ? curl_setopt($curl, CURLOPT_POST, true)
+                    : curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
                 break;
 
             case 'DELETE':
                 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
                 break;
-
-            case 'PUT':
-                if ( ! $parameters || ! is_array($parameters)) {
-                    $parameters = array();
-                }
-
-                curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($parameters));
-
-                // Suppress "Expect: 100-continue" header automatically added by cURL that
-                // causes a 1 second delay if the remote server does not support Expect.
-                $customHeaders[] = 'Expect:';
-
-                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
-                break;
         }
 
         foreach ($extraOptions as $option => $value) {

From b756195047b5e12e38b4cc6ef9add71bbd768d86 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Thu, 7 Dec 2023 17:44:55 -0500
Subject: [PATCH 36/44] fix phpstan errors

---
 lib/WebDriver/Element.php | 2 +-
 lib/WebDriver/Session.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Element.php b/lib/WebDriver/Element.php
index da15ddf2..5a625901 100644
--- a/lib/WebDriver/Element.php
+++ b/lib/WebDriver/Element.php
@@ -113,7 +113,7 @@ public function getID()
     /**
      * Get the value of an element's attribute: /session/:sessionId/element/:id/attribute/:name
      *
-     * @param string name
+     * @param string $name
      *
      * @return mixed
      */
diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index c6771e2e..aecb4f5b 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -134,7 +134,7 @@ public function open($url)
      */
     public function capabilities()
     {
-        if (! isset($this->capabilities)) {
+        if ($this->capabilities === null) {
             $result = $this->curl('GET', '');
 
             $this->capabilities = $result['value'];

From 6ca447e519517164aa457f6cfa834061ee26a591 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Thu, 7 Dec 2023 17:45:53 -0500
Subject: [PATCH 37/44] moveto shim to transform ScriptTimeout exception to
 UnknownError

---
 lib/WebDriver/Session.php | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index aecb4f5b..92c9b163 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -22,6 +22,8 @@
 
 namespace WebDriver;
 
+use WebDriver\Exception as WebDriverException;
+
 /**
  * WebDriver\Session class
  *
@@ -292,6 +294,24 @@ public function frame()
         return new Frame($this->url . '/frame');
     }
 
+    /**
+     * moveto: /session/:sessionId/moveto (POST)
+     *
+     * @param array $parameters
+     *
+     * @return mixed
+     */
+    public function moveto($parameters)
+    {
+        try {
+            $result = $this->curl('POST', '/moveto', $parameters);
+        } catch (WebDriverException\ScriptTimeout) {
+            throw WebDriverException::factory(WebDriverException::UNKNOWN_ERROR);
+        }
+
+        return $result['value'];
+    }
+
     /**
      * timeouts methods: /session/:sessionId/timeouts (POST)
      * - $session->timeouts($json) - set timeout for an operation

From a61a8459f86c79dd1f19934ea3929804f2e41f8c Mon Sep 17 00:00:00 2001
From: Phil Davis <phil@jankaritech.com>
Date: Fri, 8 Dec 2023 12:45:15 +0545
Subject: [PATCH 38/44] fix: catch ScriptTimeout exception into a variable

---
 lib/WebDriver/Session.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index 92c9b163..37ecff8f 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -48,7 +48,6 @@
  * @method void postAlert_text($jsonText) Sends keystrokes to a JavaScript prompt() dialog.
  * @method void accept_alert() Accepts the currently displayed alert dialog.
  * @method void dismiss_alert() Dismisses the currently displayed alert dialog.
- * @method void moveto($jsonCoordinates) Move the mouse by an offset of the specified element (or current mouse cursor).
  * @method void click($jsonButton) Click any mouse button (at the coordinates set by the last moveto command).
  * @method void buttondown() Click and hold the left mouse button (at the coordinates set by the last moveto command).
  * @method void buttonup() Releases the mouse button previously held (where the mouse is currently at).
@@ -305,7 +304,7 @@ public function moveto($parameters)
     {
         try {
             $result = $this->curl('POST', '/moveto', $parameters);
-        } catch (WebDriverException\ScriptTimeout) {
+        } catch (WebDriverException\ScriptTimeout $e) {
             throw WebDriverException::factory(WebDriverException::UNKNOWN_ERROR);
         }
 

From 82b1947a82000500800b0a2012f7b1c8dc9c630e Mon Sep 17 00:00:00 2001
From: Anthon Pang <anthonp@nationalfibre.net>
Date: Fri, 8 Dec 2023 09:22:36 -0500
Subject: [PATCH 39/44] fix php 5.3 incompatibility

---
 lib/WebDriver/Session.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index 37ecff8f..f8c4506d 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -498,10 +498,10 @@ private function serializeArguments(array $arguments)
                 // preferably we want to detect W3C support and never set nor parse
                 // LEGACY_ELEMENT_ID, until detection is implemented, serialize to both
                 // variants, tested with Selenium v2.53.1 and v3.141.59
-                $arguments[$key] = [
+                $arguments[$key] = array(
                     Container::WEBDRIVER_ELEMENT_ID => $value->getID(),
                     Container::LEGACY_ELEMENT_ID => $value->getID(),
-                ];
+                );
                 continue;
             }
 

From 55ac666f870994762451675c37b0f3009b2e54ab Mon Sep 17 00:00:00 2001
From: Anthon Pang <anthonp@nationalfibre.net>
Date: Fri, 8 Dec 2023 09:32:01 -0500
Subject: [PATCH 40/44] CurlService should set its headers first so user's
 extraOptions can override

---
 lib/WebDriver/Service/CurlService.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/WebDriver/Service/CurlService.php b/lib/WebDriver/Service/CurlService.php
index 4c5fa1f1..1462fb51 100755
--- a/lib/WebDriver/Service/CurlService.php
+++ b/lib/WebDriver/Service/CurlService.php
@@ -51,8 +51,8 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
 
             case 'POST':
             case 'PUT':
-                $parameters =  ! $parameters || ! is_array($parameters)
-                    ? '{}'
+                $parameters = ! $parameters || ! is_array($parameters)
+                    ? '{}' // instead of json_encode(new \stdclass))
                     : json_encode($parameters);
 
                 curl_setopt($curl, CURLOPT_POSTFIELDS, $parameters);
@@ -71,12 +71,12 @@ public function execute($requestMethod, $url, $parameters = null, $extraOptions
                 break;
         }
 
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $customHeaders);
+
         foreach ($extraOptions as $option => $value) {
             curl_setopt($curl, $option, $value);
         }
 
-        curl_setopt($curl, CURLOPT_HTTPHEADER, $customHeaders);
-
         $rawResult = trim(curl_exec($curl));
 
         $info = curl_getinfo($curl);

From 81930949a4bfb63a7a3218fff0606818d36b05e7 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Sat, 9 Dec 2023 15:12:11 -0500
Subject: [PATCH 41/44] methods expected to be in an array

---
 lib/WebDriver/WebDriver.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/WebDriver/WebDriver.php b/lib/WebDriver/WebDriver.php
index b3cd9b5b..81c85312 100644
--- a/lib/WebDriver/WebDriver.php
+++ b/lib/WebDriver/WebDriver.php
@@ -37,7 +37,7 @@ class WebDriver extends AbstractWebDriver implements WebDriverInterface
     protected function methods()
     {
         return array(
-            'status' => 'GET',
+            'status' => array('GET'),
         );
     }
 

From bac541ca487175e8fddf1da9571fed2368ea178c Mon Sep 17 00:00:00 2001
From: Alex <aik099@users.noreply.github.com>
Date: Fri, 23 Feb 2024 16:33:21 +0200
Subject: [PATCH 42/44] Load Mink Selenium 2 Driver package from source to have
 tests

---
 .github/workflows/ci.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bcc8dd43..65593b75 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,10 +30,12 @@ jobs:
       - name: Install dependencies
         run: |
           composer config version 1.4.99
+          composer config preferred-install.behat/mink-selenium2-driver source
+          composer config preferred-install.* dist         
           composer require --no-update behat/mink-selenium2-driver:dev-master --dev --quiet
           composer require --no-update mink/driver-testsuite:dev-master --dev --quiet
           php -r '$json = json_decode(file_get_contents ("composer.json"), true); $json["autoload"]["psr-4"]["Behat\\Mink\\Tests\\Driver\\"] = "vendor/behat/mink-selenium2-driver/tests/"; file_put_contents("composer.json", json_encode($json, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT));'
-          composer update --no-interaction --prefer-dist
+          composer update --no-interaction
 
       - name: Start Selenium & Mink test server
         run: |

From b5f917203d27cf34d7a576c950b27b273746f663 Mon Sep 17 00:00:00 2001
From: Alex <aik099@users.noreply.github.com>
Date: Fri, 23 Feb 2024 11:07:56 +0200
Subject: [PATCH 43/44] Allow frame switching to WebElement

---
 lib/WebDriver/Session.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/WebDriver/Session.php b/lib/WebDriver/Session.php
index f8c4506d..c10e855e 100644
--- a/lib/WebDriver/Session.php
+++ b/lib/WebDriver/Session.php
@@ -282,7 +282,7 @@ public function focusWindow($name)
     public function frame()
     {
         if (func_num_args() === 1) {
-            $arg = func_get_arg(0); // json
+            $arg = $this->serializeArguments(func_get_arg(0)); // json
 
             $this->curl('POST', '/frame', $arg);
 

From 3b2a2ddc4e0a690cc691d7e5952964cc4b9538b1 Mon Sep 17 00:00:00 2001
From: Anthon Pang <apang@softwaredevelopment.ca>
Date: Mon, 18 Mar 2024 21:58:53 -0400
Subject: [PATCH 44/44] ci cosmetic: remove trailing whitespace

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 65593b75..a0ea0ab3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
         run: |
           composer config version 1.4.99
           composer config preferred-install.behat/mink-selenium2-driver source
-          composer config preferred-install.* dist         
+          composer config preferred-install.* dist
           composer require --no-update behat/mink-selenium2-driver:dev-master --dev --quiet
           composer require --no-update mink/driver-testsuite:dev-master --dev --quiet
           php -r '$json = json_decode(file_get_contents ("composer.json"), true); $json["autoload"]["psr-4"]["Behat\\Mink\\Tests\\Driver\\"] = "vendor/behat/mink-selenium2-driver/tests/"; file_put_contents("composer.json", json_encode($json, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT));'
