1<?php
2
3namespace Facebook\WebDriver\Remote;
4
5use Exception;
6use Facebook\WebDriver\Chrome\ChromeOptions;
7use Facebook\WebDriver\Firefox\FirefoxDriver;
8use Facebook\WebDriver\Firefox\FirefoxOptions;
9use Facebook\WebDriver\Firefox\FirefoxProfile;
10use Facebook\WebDriver\WebDriverCapabilities;
11use Facebook\WebDriver\WebDriverPlatform;
12
13class DesiredCapabilities implements WebDriverCapabilities
14{
15    /** @var array */
16    private $capabilities;
17
18    /** @var array */
19    private static $ossToW3c = [
20        WebDriverCapabilityType::PLATFORM => 'platformName',
21        WebDriverCapabilityType::VERSION => 'browserVersion',
22        WebDriverCapabilityType::ACCEPT_SSL_CERTS => 'acceptInsecureCerts',
23        ChromeOptions::CAPABILITY => ChromeOptions::CAPABILITY_W3C,
24    ];
25
26    public function __construct(array $capabilities = [])
27    {
28        $this->capabilities = $capabilities;
29    }
30
31    public static function createFromW3cCapabilities(array $capabilities = [])
32    {
33        $w3cToOss = array_flip(self::$ossToW3c);
34
35        foreach ($w3cToOss as $w3cCapability => $ossCapability) {
36            // Copy W3C capabilities to OSS ones
37            if (array_key_exists($w3cCapability, $capabilities)) {
38                $capabilities[$ossCapability] = $capabilities[$w3cCapability];
39            }
40        }
41
42        return new self($capabilities);
43    }
44
45    /**
46     * @return string The name of the browser.
47     */
48    public function getBrowserName()
49    {
50        return $this->get(WebDriverCapabilityType::BROWSER_NAME, '');
51    }
52
53    /**
54     * @param string $browser_name
55     * @return DesiredCapabilities
56     */
57    public function setBrowserName($browser_name)
58    {
59        $this->set(WebDriverCapabilityType::BROWSER_NAME, $browser_name);
60
61        return $this;
62    }
63
64    /**
65     * @return string The version of the browser.
66     */
67    public function getVersion()
68    {
69        return $this->get(WebDriverCapabilityType::VERSION, '');
70    }
71
72    /**
73     * @param string $version
74     * @return DesiredCapabilities
75     */
76    public function setVersion($version)
77    {
78        $this->set(WebDriverCapabilityType::VERSION, $version);
79
80        return $this;
81    }
82
83    /**
84     * @param string $name
85     * @return mixed The value of a capability.
86     */
87    public function getCapability($name)
88    {
89        return $this->get($name);
90    }
91
92    /**
93     * @param string $name
94     * @param mixed $value
95     * @return DesiredCapabilities
96     */
97    public function setCapability($name, $value)
98    {
99        // When setting 'moz:firefoxOptions' from an array and not from instance of FirefoxOptions, we must merge
100        // it with default FirefoxOptions to keep previous behavior (where the default preferences were added
101        // using FirefoxProfile, thus not overwritten by adding 'moz:firefoxOptions')
102        // TODO: remove in next major version, once FirefoxOptions are only accepted as object instance and not as array
103        if ($name === FirefoxOptions::CAPABILITY && is_array($value)) {
104            $defaultOptions = (new FirefoxOptions())->toArray();
105            $value = array_merge($defaultOptions, $value);
106        }
107
108        $this->set($name, $value);
109
110        return $this;
111    }
112
113    /**
114     * @return string The name of the platform.
115     */
116    public function getPlatform()
117    {
118        return $this->get(WebDriverCapabilityType::PLATFORM, '');
119    }
120
121    /**
122     * @param string $platform
123     * @return DesiredCapabilities
124     */
125    public function setPlatform($platform)
126    {
127        $this->set(WebDriverCapabilityType::PLATFORM, $platform);
128
129        return $this;
130    }
131
132    /**
133     * @param string $capability_name
134     * @return bool Whether the value is not null and not false.
135     */
136    public function is($capability_name)
137    {
138        return (bool) $this->get($capability_name);
139    }
140
141    /**
142     * @todo Remove in next major release (BC)
143     * @deprecated All browsers are always JS enabled except HtmlUnit and it's not meaningful to disable JS execution.
144     * @return bool Whether javascript is enabled.
145     */
146    public function isJavascriptEnabled()
147    {
148        return $this->get(WebDriverCapabilityType::JAVASCRIPT_ENABLED, false);
149    }
150
151    /**
152     * This is a htmlUnit-only option.
153     *
154     * @param bool $enabled
155     * @throws Exception
156     * @return DesiredCapabilities
157     * @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities
158     */
159    public function setJavascriptEnabled($enabled)
160    {
161        $browser = $this->getBrowserName();
162        if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) {
163            throw new Exception(
164                'isJavascriptEnabled() is a htmlunit-only option. ' .
165                'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.'
166            );
167        }
168
169        $this->set(WebDriverCapabilityType::JAVASCRIPT_ENABLED, $enabled);
170
171        return $this;
172    }
173
174    /**
175     * @todo Remove side-effects - not change eg. ChromeOptions::CAPABILITY from instance of ChromeOptions to an array
176     * @return array
177     */
178    public function toArray()
179    {
180        if (isset($this->capabilities[ChromeOptions::CAPABILITY]) &&
181            $this->capabilities[ChromeOptions::CAPABILITY] instanceof ChromeOptions
182        ) {
183            $this->capabilities[ChromeOptions::CAPABILITY] =
184                $this->capabilities[ChromeOptions::CAPABILITY]->toArray();
185        }
186
187        if (isset($this->capabilities[FirefoxOptions::CAPABILITY]) &&
188            $this->capabilities[FirefoxOptions::CAPABILITY] instanceof FirefoxOptions
189        ) {
190            $this->capabilities[FirefoxOptions::CAPABILITY] =
191                $this->capabilities[FirefoxOptions::CAPABILITY]->toArray();
192        }
193
194        if (isset($this->capabilities[FirefoxDriver::PROFILE]) &&
195            $this->capabilities[FirefoxDriver::PROFILE] instanceof FirefoxProfile
196        ) {
197            $this->capabilities[FirefoxDriver::PROFILE] =
198                $this->capabilities[FirefoxDriver::PROFILE]->encode();
199        }
200
201        return $this->capabilities;
202    }
203
204    /**
205     * @return array
206     */
207    public function toW3cCompatibleArray()
208    {
209        $allowedW3cCapabilities = [
210            'browserName',
211            'browserVersion',
212            'platformName',
213            'acceptInsecureCerts',
214            'pageLoadStrategy',
215            'proxy',
216            'setWindowRect',
217            'timeouts',
218            'strictFileInteractability',
219            'unhandledPromptBehavior',
220        ];
221
222        $ossCapabilities = $this->toArray();
223        $w3cCapabilities = [];
224
225        foreach ($ossCapabilities as $capabilityKey => $capabilityValue) {
226            // Copy already W3C compatible capabilities
227            if (in_array($capabilityKey, $allowedW3cCapabilities, true)) {
228                $w3cCapabilities[$capabilityKey] = $capabilityValue;
229            }
230
231            // Convert capabilities with changed name
232            if (array_key_exists($capabilityKey, self::$ossToW3c)) {
233                if ($capabilityKey === WebDriverCapabilityType::PLATFORM) {
234                    $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue);
235
236                    // Remove platformName if it is set to "any"
237                    if ($w3cCapabilities[self::$ossToW3c[$capabilityKey]] === 'any') {
238                        unset($w3cCapabilities[self::$ossToW3c[$capabilityKey]]);
239                    }
240                } else {
241                    $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = $capabilityValue;
242                }
243            }
244
245            // Copy vendor extensions
246            if (mb_strpos($capabilityKey, ':') !== false) {
247                $w3cCapabilities[$capabilityKey] = $capabilityValue;
248            }
249        }
250
251        // Convert ChromeOptions
252        if (array_key_exists(ChromeOptions::CAPABILITY, $ossCapabilities)) {
253            if (array_key_exists(ChromeOptions::CAPABILITY_W3C, $ossCapabilities)) {
254                $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = new \ArrayObject(
255                    array_merge_recursive(
256                        (array) $ossCapabilities[ChromeOptions::CAPABILITY],
257                        (array) $ossCapabilities[ChromeOptions::CAPABILITY_W3C]
258                    )
259                );
260            } else {
261                $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = $ossCapabilities[ChromeOptions::CAPABILITY];
262            }
263        }
264
265        // Convert Firefox profile
266        if (array_key_exists(FirefoxDriver::PROFILE, $ossCapabilities)) {
267            // Convert profile only if not already set in moz:firefoxOptions
268            if (!array_key_exists(FirefoxOptions::CAPABILITY, $ossCapabilities)
269                || !array_key_exists('profile', $ossCapabilities[FirefoxOptions::CAPABILITY])) {
270                $w3cCapabilities[FirefoxOptions::CAPABILITY]['profile'] = $ossCapabilities[FirefoxDriver::PROFILE];
271            }
272        }
273
274        return $w3cCapabilities;
275    }
276
277    /**
278     * @return static
279     */
280    public static function android()
281    {
282        return new static([
283            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::ANDROID,
284            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANDROID,
285        ]);
286    }
287
288    /**
289     * @return static
290     */
291    public static function chrome()
292    {
293        return new static([
294            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME,
295            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
296        ]);
297    }
298
299    /**
300     * @return static
301     */
302    public static function firefox()
303    {
304        $caps = new static([
305            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::FIREFOX,
306            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
307        ]);
308
309        $caps->setCapability(FirefoxOptions::CAPABILITY, new FirefoxOptions()); // to add default options
310
311        return $caps;
312    }
313
314    /**
315     * @return static
316     */
317    public static function htmlUnit()
318    {
319        return new static([
320            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT,
321            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
322        ]);
323    }
324
325    /**
326     * @return static
327     */
328    public static function htmlUnitWithJS()
329    {
330        $caps = new static([
331            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT,
332            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
333        ]);
334
335        return $caps->setJavascriptEnabled(true);
336    }
337
338    /**
339     * @return static
340     */
341    public static function internetExplorer()
342    {
343        return new static([
344            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IE,
345            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS,
346        ]);
347    }
348
349    /**
350     * @return static
351     */
352    public static function microsoftEdge()
353    {
354        return new static([
355            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::MICROSOFT_EDGE,
356            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS,
357        ]);
358    }
359
360    /**
361     * @return static
362     */
363    public static function iphone()
364    {
365        return new static([
366            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPHONE,
367            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC,
368        ]);
369    }
370
371    /**
372     * @return static
373     */
374    public static function ipad()
375    {
376        return new static([
377            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPAD,
378            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC,
379        ]);
380    }
381
382    /**
383     * @return static
384     */
385    public static function opera()
386    {
387        return new static([
388            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::OPERA,
389            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
390        ]);
391    }
392
393    /**
394     * @return static
395     */
396    public static function safari()
397    {
398        return new static([
399            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::SAFARI,
400            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
401        ]);
402    }
403
404    /**
405     * @deprecated PhantomJS is no longer developed and its support will be removed in next major version.
406     * Use headless Chrome or Firefox instead.
407     * @return static
408     */
409    public static function phantomjs()
410    {
411        return new static([
412            WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::PHANTOMJS,
413            WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
414        ]);
415    }
416
417    /**
418     * @param string $key
419     * @param mixed $value
420     * @return DesiredCapabilities
421     */
422    private function set($key, $value)
423    {
424        $this->capabilities[$key] = $value;
425
426        return $this;
427    }
428
429    /**
430     * @param string $key
431     * @param mixed $default
432     * @return mixed
433     */
434    private function get($key, $default = null)
435    {
436        return isset($this->capabilities[$key])
437            ? $this->capabilities[$key]
438            : $default;
439    }
440}
441