1
|
|
|
<?php |
2
|
|
|
namespace Codeception\Extension; |
3
|
|
|
|
4
|
|
|
use Codeception\Module; |
5
|
|
|
use Codeception\Exception\ModuleException; |
6
|
|
|
use Codeception\Exception\ModuleConfigException; |
7
|
|
|
use \PHPBrowserMobProxy_Client as BMP; |
8
|
|
|
use \Requests; |
9
|
|
|
use \RuntimeException; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* @method void _open() |
13
|
|
|
* @method void _close() |
14
|
|
|
* @method string _newHar(string $label='') |
15
|
|
|
* @method string _newPage(string $label='') |
16
|
|
|
* @method string _blacklist(string $regexp, integer $status_code) |
17
|
|
|
* @method string _whitelist(string $regexp, integer $status_code) |
18
|
|
|
* @method string _basicAuth(string $domain, string[] $options) |
19
|
|
|
* @method string _headers(string[] $options) |
20
|
|
|
* @method string _responseInterceptor(string $js) |
21
|
|
|
* @method string _requestInterceptor(string $js) |
22
|
|
|
* @method Requests_Response _limits(string[] $options) |
23
|
|
|
* @method Requests_Response _timeouts(string[] $options) |
24
|
|
|
* @method string _remapHosts(string $address, string $ip_address) |
25
|
|
|
* @method string _waitForTrafficToStop(integer $quiet_period, integer $timeout) |
26
|
|
|
* @method string _clearDnsCache() |
27
|
|
|
* @method string _rewriteUrl(string $match, string $replace) |
28
|
|
|
* @method string _retry(integer $retry_count) |
29
|
|
|
*/ |
30
|
|
|
class BrowserMob extends Module |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
protected $config = ['host', 'port', 'autostart', 'blacklist', 'whitelist', 'limits', 'timeouts', 'redirect', 'retry', 'basicAuth', 'littleproxy']; |
34
|
|
|
|
35
|
|
|
protected $requiredFields = ['host']; |
36
|
|
|
|
37
|
|
|
protected $response; |
38
|
|
|
|
39
|
|
|
private $bmp; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @codeCoverageIgnore |
43
|
|
|
* @ignore Codeception specific |
44
|
|
|
*/ |
45
|
|
|
public function _initialize() |
46
|
|
|
{ |
47
|
|
|
$host = $this->config['host']; |
48
|
|
|
if (isset($this->config['port'])) { |
49
|
|
|
$host = $host.':'.$this->config['port']; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
// test if proxy is available |
53
|
|
|
if (static::__pingProxy($host)) { |
54
|
|
|
$this->bmp = new BMP($host); |
55
|
|
|
} else { |
56
|
|
|
throw new ModuleConfigException(__CLASS__, "Proxy '{$host}' cannot be reached"); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
// start a new BrowserMobProxy session |
60
|
|
|
if (isset($this->config['autostart'])) { |
61
|
|
|
if (true === (bool) $this->config['autostart']) { |
62
|
|
|
$this->openProxy(); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
protected static function __pingProxy($url) |
68
|
|
|
{ |
69
|
|
|
try { |
70
|
|
|
$response = Requests::get('http://'.$url.'/proxy/'); |
71
|
|
|
} catch (\Exception $e) { |
72
|
|
|
throw new ModuleException(__CLASS__, $e->getMessage()); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return $response->success; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
protected function __setProxyCapabilities($capabilities) |
79
|
|
|
{ |
80
|
|
|
$response = null; |
81
|
|
|
|
82
|
|
|
foreach ($capabilities as $config => $data) { |
83
|
|
|
try { |
84
|
|
|
if (false === empty($data)) { |
85
|
|
|
switch ((string) $config) { |
86
|
|
|
case 'blacklist': |
87
|
|
|
foreach ($data['patterns'] as $pattern) { |
88
|
|
|
$response = $this->_blacklist($pattern, $data['code']); |
89
|
|
|
if (isset($response->success)) { |
90
|
|
|
if (false === $response->success) { |
91
|
|
|
break; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
break; |
96
|
|
|
case 'whitelist': |
97
|
|
|
$patterns = implode(',', $data['patterns']); |
98
|
|
|
$response = $this->_whitelist($patterns, $data['code']); |
99
|
|
|
if (isset($response->success)) { |
100
|
|
|
if (false === $response->success) { |
101
|
|
|
break; |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
break; |
105
|
|
|
case 'limits': |
106
|
|
|
$response = $this->_limits($data); |
107
|
|
|
break; |
108
|
|
|
case 'timeouts': |
109
|
|
|
$response = $this->_timeouts($data); |
110
|
|
|
break; |
111
|
|
|
case 'redirect': |
112
|
|
|
foreach ($data as $entry) { |
113
|
|
|
$response = $this->_remapHosts($entry['address'], $entry['ip']); |
114
|
|
|
if (isset($response->success)) { |
115
|
|
|
if (false === $response->success) { |
116
|
|
|
break; |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
break; |
121
|
|
|
case 'retry': |
122
|
|
|
$response = $this->_retry($data); |
123
|
|
|
break; |
124
|
|
|
case 'basicAuth': |
125
|
|
|
foreach ($data as $entry) { |
126
|
|
|
$response = $this->_basicAuth($entry['domain'], $entry['options']); |
127
|
|
|
if (isset($response->success)) { |
128
|
|
|
if (false === $response->success) { |
129
|
|
|
break; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
break; |
134
|
|
|
default: |
135
|
|
|
// do nothing |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
} catch (\Exception $e) { |
139
|
|
|
throw new ModuleConfigException(__CLASS__, $e->getMessage()); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
if (get_class($response) === 'Request') |
143
|
|
|
{ |
144
|
|
|
$this->response = $response; |
145
|
|
|
if (false === $response->success) { |
146
|
|
|
throw new ModuleConfigException(__CLASS__, "Proxy response error '{$response->status_code}' {$respone->body}"); |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
public function getProxyPort() |
153
|
|
|
{ |
154
|
|
|
return $this->bmp->port; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
public function openProxy($capabilities = null) |
158
|
|
|
{ |
159
|
|
|
try { |
160
|
|
|
$this->bmp->open(); |
161
|
|
|
if (empty($capabilities)) { |
162
|
|
|
$capabilities = $this->config; |
163
|
|
|
} |
164
|
|
|
$this->__setProxyCapabilities($capabilities); |
165
|
|
|
} catch (\Exception $e) { |
166
|
|
|
throw new ModuleException(__CLASS__, $e->getMessage()); |
167
|
|
|
} |
168
|
|
|
return $this->getProxyPort(); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function closeProxy() |
172
|
|
|
{ |
173
|
|
|
try { |
174
|
|
|
$this->bmp->close(); |
175
|
|
|
} catch (\Exception $e) { |
176
|
|
|
throw new ModuleException(__CLASS__, $e->getMessage()); |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|
180
|
|
View Code Duplication |
public function startHar() |
|
|
|
|
181
|
|
|
{ |
182
|
|
|
try { |
183
|
|
|
$this->response = $this->bmp->newHar(); |
184
|
|
|
} catch (\Exception $e) { |
185
|
|
|
throw new ModuleException(__CLASS__, $e->getMessage()); |
186
|
|
|
} |
187
|
|
|
return $this->response->success; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
View Code Duplication |
public function startPage() |
|
|
|
|
191
|
|
|
{ |
192
|
|
|
try { |
193
|
|
|
$this->response = $this->bmp->newPage(); |
194
|
|
|
} catch (\Exception $e) { |
195
|
|
|
throw new ModuleException(__CLASS__, $e->getMessage()); |
196
|
|
|
} |
197
|
|
|
return $this->response->success; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
public function getHar() |
201
|
|
|
{ |
202
|
|
|
return $this->bmp->har; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
// magic function that exposes BrowserMobProxy API pulic methods |
206
|
|
|
public function __call($method, $args) |
207
|
|
|
{ |
208
|
|
|
// check if is a command call |
209
|
|
|
if (preg_match('/^_[A-z]+$/', $method)) { |
210
|
|
|
// extract standard method name |
211
|
|
|
$method = preg_filter('/_/', '', $method); |
212
|
|
|
// set call array for calling method |
213
|
|
|
$call = array($this->bmp, $method); |
214
|
|
|
// check if method is callable |
215
|
|
|
if (is_callable($call)) { |
216
|
|
|
$ret = call_user_func_array($call, $args); |
217
|
|
|
} else { |
218
|
|
|
throw new RuntimeException("Method ${method} does not exist or is not callable"); |
219
|
|
|
} |
220
|
|
|
} else { |
221
|
|
|
throw new RuntimeException("Method ${method} does not exist or is not callable"); |
222
|
|
|
} |
223
|
|
|
$ret = (isset($ret)) ? $ret : null; |
224
|
|
|
return $ret; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.