Issues (3641)

Shared/Testify/_support/Helper/WebDriverHelper.php (1 issue)

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace SprykerTest\Shared\Testify\Helper;
9
10
use Codeception\Event\SuiteEvent;
11
use Codeception\Events;
12
use Codeception\Exception\ExtensionException;
13
use Codeception\Extension;
14
use Codeception\Module\WebDriver;
15
16
class WebDriverHelper extends Extension
17
{
18
    /**
19
     * @var string
20
     */
21
    protected const MODULE_NAME_WEBDRIVER = 'WebDriver';
22
23
    /**
24
     * @var string
25
     */
26
    protected const KEY_REMOTE_ENABLE = 'remote-enable';
27
28
    /**
29
     * @var string
30
     */
31
    protected const KEY_HOST = 'host';
32
33
    /**
34
     * @var string
35
     */
36
    protected const KEY_PORT = 'port';
37
38
    /**
39
     * @var string
40
     */
41
    protected const KEY_BROWSER = 'browser';
42
43
    /**
44
     * @var string
45
     */
46
    protected const KEY_CAPABILITIES = 'capabilities';
47
48
    /**
49
     * @var string
50
     */
51
    protected const DEFAULT_HOST = '0.0.0.0';
52
53
    /**
54
     * @var int
55
     */
56
    protected const DEFAULT_PORT = 4444;
57
58
    /**
59
     * @var string
60
     */
61
    protected const DEFAULT_BROWSER = 'phantomjs';
62
63
    /**
64
     * @var string
65
     */
66
    protected const DEFAULT_PATH = 'vendor/bin/phantomjs';
67
68
    /**
69
     * @var int
70
     */
71
    protected const DEFAULT_TIMEOUT = 10;
72
73
    /**
74
     * @var array
75
     */
76
    protected const BROWSER_PARAMETERS = [
77
        'vendor/bin/chromedriver' => [
78
            'webdriver-port' => '--port',
79
            'whitelisted-ips' => '--whitelisted-ips',
80
            'url-base' => '--url-base',
81
        ],
82
        'vendor/bin/phantomjs' => [
83
            'port' => '--webdriver',
84
            'proxy' => '--proxy',
85
            'proxyType' => '--proxy-type',
86
            'proxyAuth' => '--proxy-auth',
87
            'webSecurity' => '--web-security',
88
            'ignoreSslErrors' => '--ignore-ssl-errors',
89
            'sslProtocol' => '--ssl-protocol',
90
            'sslCertificatesPath' => '--ssl-certificates-path',
91
            'remoteDebuggerPort' => '--remote-debugger-port',
92
            'remoteDebuggerAutorun' => '--remote-debugger-autorun',
93
            'cookiesFile' => '--cookies-file',
94
            'diskCache' => '--disk-cache',
95
            'maxDiskCacheSize' => '--max-disk-cache-size',
96
            'loadImages' => '--load-images',
97
            'localStoragePath' => '--local-storage-path',
98
            'localStorageQuota' => '--local-storage-quota',
99
            'localToRemoteUrlAccess' => '--local-to-remote-url-access',
100
            'outputEncoding' => '--output-encoding',
101
            'scriptEncoding' => '--script-encoding',
102
            'webdriverLoglevel' => '--webdriver-loglevel',
103
            'webdriverLogfile' => '--webdriver-logfile',
104
        ],
105
    ];
106
107
    /**
108
     * @var array<string>
109
     */
110
    public static $events = [
111
        Events::SUITE_INIT => 'suiteInit',
112
        Events::SUITE_BEFORE => 'configureWebDriverModule',
113
    ];
114
115
    /**
116
     * @var resource
117
     */
118
    protected $resource;
119
120
    /**
121
     * @var array
122
     */
123
    protected $pipes;
124
125
    /**
126
     * @param array $config
127
     * @param array<string, mixed> $options
128
     */
129
    public function __construct(array $config, array $options)
130
    {
131
        if (isset($config['silent']) && $config['silent']) {
132
            $options['silent'] = true;
133
        }
134
135
        parent::__construct($config, $options);
136
137
        if (!isset($this->config['path'])) {
138
            $this->config['path'] = static::DEFAULT_PATH;
0 ignored issues
show
Bug Best Practice introduced by
The property config does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
139
        }
140
141
        if (!isset($this->config['port'])) {
142
            $this->config['port'] = static::DEFAULT_PORT;
143
        }
144
145
        if (!isset($this->config['debug'])) {
146
            $this->config['debug'] = false;
147
        }
148
    }
149
150
    public function __destruct()
151
    {
152
        $this->stopServer();
153
    }
154
155
    /**
156
     * @param \Codeception\Event\SuiteEvent $e
157
     *
158
     * @throws \Codeception\Exception\ExtensionException
159
     *
160
     * @return void
161
     */
162
    public function suiteInit(SuiteEvent $e): void
163
    {
164
        if (!$this->isRemoteEnabled()) {
165
            if (!file_exists(realpath($this->config['path']))) {
166
                throw new ExtensionException(
167
                    $this,
168
                    sprintf('Webdriver executable not found: %s', $this->config['path']),
169
                );
170
            }
171
172
            if (isset($this->config['suites'])) {
173
                if (is_string($this->config['suites'])) {
174
                    $suites = [$this->config['suites']];
175
                } else {
176
                    $suites = $this->config['suites'];
177
                }
178
179
                if (
180
                    !in_array($e->getSuite()->getBaseName(), $suites, true)
181
                    && !in_array($e->getSuite()->getName(), $suites, true)
182
                ) {
183
                    return;
184
                }
185
            }
186
187
            $this->startServer();
188
        }
189
    }
190
191
    /**
192
     * @return void
193
     */
194
    public function configureWebDriverModule(): void
195
    {
196
        if (!$this->hasModule(static::MODULE_NAME_WEBDRIVER)) {
197
            return;
198
        }
199
200
        $this->getWebDriver()->_reconfigure(
201
            $this->getWebDriverConfig(),
202
        );
203
    }
204
205
    /**
206
     * @throws \Codeception\Exception\ExtensionException
207
     *
208
     * @return void
209
     */
210
    protected function startServer(): void
211
    {
212
        if ($this->resource !== null) {
213
            return;
214
        }
215
216
        $this->writeln(PHP_EOL);
217
        $this->writeln('Starting webdriver server.');
218
219
        $command = $this->getCommand();
220
221
        if ($this->config['debug']) {
222
            $this->writeln(PHP_EOL);
223
224
            $this->writeln('Generated webdriver command:');
225
            $this->writeln($command);
226
            $this->writeln(PHP_EOL);
227
        }
228
229
        $descriptorSpec = [
230
            ['pipe', 'r'],
231
            ['file', $this->getLogDir() . 'webdriver.output.txt', 'w'],
232
            ['file', $this->getLogDir() . 'webdriver.errors.txt', 'a'],
233
        ];
234
235
        $this->resource = proc_open(
236
            $command,
237
            $descriptorSpec,
238
            $this->pipes,
239
            null,
240
            null,
241
            ['bypass_shell' => true],
242
        );
243
244
        // phpcs:disable
245
        if (!is_resource($this->resource) || !proc_get_status($this->resource)['running']) {
246
            // phpcs:enable
247
            proc_close($this->resource);
248
249
            throw new ExtensionException($this, 'Failed to start webdriver server.');
250
        }
251
252
        $max_checks = 10;
253
        $checks = 0;
254
255
        $this->write('Waiting for the webdriver server to be reachable.');
256
257
        while (true) {
258
            if ($checks >= $max_checks) {
259
                throw new ExtensionException($this, 'Webdriver server never became reachable.');
260
            }
261
262
            // phpcs:disable
263
            $fp = @fsockopen(
264
                '127.0.0.1',
265
                $this->config['port'],
266
                $errCode,
267
                $errStr,
268
                static::DEFAULT_TIMEOUT
269
            );
270
            // phpcs:enable
271
            if ($fp) {
272
                $this->writeln('');
273
                $this->writeln('Webdriver server now accessible.');
274
                fclose($fp);
275
276
                break;
277
            }
278
279
            $this->write('.');
280
            $checks++;
281
            sleep(1);
282
        }
283
284
        $this->writeln('');
285
    }
286
287
    /**
288
     * @return void
289
     */
290
    protected function stopServer(): void
291
    {
292
        if ($this->resource !== null) {
293
            $this->write('Stopping webdriver server.');
294
            $max_checks = 10;
295
296
            for ($i = 0; $i < $max_checks; $i++) {
297
                if ($i === $max_checks - 1 && proc_get_status($this->resource)['running'] === true) {
298
                    $this->writeln('');
299
                    $this->writeln('Unable to properly shutdown webdriver server.');
300
                    unset($this->resource);
301
302
                    break;
303
                }
304
305
                if (proc_get_status($this->resource)['running'] === false) {
306
                    $this->writeln('');
307
                    $this->writeln('Webdriver server stopped.');
308
                    unset($this->resource);
309
310
                    break;
311
                }
312
313
                foreach ($this->pipes as $pipe) {
314
                    // phpcs:disable
315
                    if (is_resource($pipe)) {
316
                        fclose($pipe);
317
                    }
318
                    // phpcs:enable
319
                }
320
321
                proc_terminate($this->resource, 2);
322
                $this->write('.');
323
                sleep(1);
324
            }
325
        }
326
    }
327
328
    /**
329
     * @throws \Codeception\Exception\ExtensionException
330
     *
331
     * @return array<string>
332
     */
333
    protected function getCommandParametersMapping(): array
334
    {
335
        $browser_path = $this->config['path'];
336
337
        if (!empty(static::BROWSER_PARAMETERS[$browser_path])) {
338
            return static::BROWSER_PARAMETERS[$browser_path];
339
        }
340
341
        throw new ExtensionException($this, 'Unknown browser specified: ' . $browser_path);
342
    }
343
344
    /**
345
     * @return string
346
     */
347
    protected function getCommandParameters(): string
348
    {
349
        $mapping = $this->getCommandParametersMapping();
350
        $params = [];
351
352
        foreach ($this->config as $configKey => $configValue) {
353
            if (!empty($mapping[$configKey])) {
354
                if (is_bool($configValue)) {
355
                    $configValue = $configValue ? 'true' : 'false';
356
                }
357
                $params[] = $mapping[$configKey] . '=' . $configValue;
358
            }
359
        }
360
361
        return implode(' ', $params);
362
    }
363
364
    /**
365
     * @return string
366
     */
367
    protected function getCommand(): string
368
    {
369
        return 'exec ' . escapeshellarg(realpath($this->config['path'])) . ' ' . $this->getCommandParameters();
370
    }
371
372
    /**
373
     * @return array<string, mixed>
374
     */
375
    protected function getWebDriverConfig(): array
376
    {
377
        $webdriverConfig = [];
378
379
        $webdriverConfig[static::KEY_HOST] = $this->config[static::KEY_HOST] ?? static::DEFAULT_HOST;
380
        $webdriverConfig[static::KEY_PORT] = $this->config[static::KEY_PORT] ?? static::DEFAULT_PORT;
381
        $webdriverConfig[static::KEY_BROWSER] = $this->config[static::KEY_BROWSER] ?? static::DEFAULT_BROWSER;
382
        $webdriverConfig[static::KEY_CAPABILITIES] = $this->config[static::KEY_CAPABILITIES] ?? [];
383
384
        return $webdriverConfig;
385
    }
386
387
    /**
388
     * @return \Codeception\Module\WebDriver
389
     */
390
    protected function getWebDriver(): WebDriver
391
    {
392
        return $this->getModule(static::MODULE_NAME_WEBDRIVER);
393
    }
394
395
    /**
396
     * @return bool
397
     */
398
    protected function isRemoteEnabled(): bool
399
    {
400
        $isRemoteEnabled = $this->config[static::KEY_REMOTE_ENABLE] ?? false;
401
402
        if ($isRemoteEnabled === 'false') {
403
            return false;
404
        }
405
406
        return (bool)$isRemoteEnabled;
407
    }
408
}
409