├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src ├── ConnectionConfig.php ├── ContextConfig.php ├── Exception ├── ConnectionError.php └── JsError.php ├── Js ├── Api │ ├── Context.php │ ├── ElementHandle.php │ ├── Frame.php │ ├── Page.php │ └── Stuctures │ │ ├── BoundingBox.php │ │ ├── Header.php │ │ ├── NativeValue.php │ │ └── SelectOption.php ├── Builder.php ├── Constructions.php ├── Functions.php ├── Script.php └── Vars.php ├── TaskResponse.php └── TaskServer.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | 4 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 LuKa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # playwright-php 2 | 3 | 4 | Install with composer 5 | ```bash 6 | composer require luka-dev/playwright-php 7 | ``` 8 | 9 | A PHP Module, that help with generating of task script for playwright and send it node.js server 10 | 11 | All detailed information [here](https://github.com/luka-dev/playwright-task-server) 12 | 13 | Code example 14 | ```php 15 | $connectionConfig = new \ConnectionConfig('localhost'); 16 | 17 | $contextConfig = new ContextConfig(); 18 | 19 | $contextConfig->setUserAgent('Custom UserAgent'); 20 | $contextConfig->setProxy( 21 | 'protocol://address:port', 22 | null, 23 | 'username', 24 | 'password' 25 | ); 26 | 27 | 28 | $taskServer = new \TaskServer($connectionConfig, $contextConfig); 29 | 30 | $script = new Context(); 31 | 32 | $page = $script->newPage(); 33 | $page->goto('https://2ip.ru/'); 34 | $element = $page->query('div.ip'); 35 | $element->textContentToVar('ip'); 36 | $script->resolve('ip'); 37 | 38 | $response = $taskServer->runTask($script); 39 | ``` 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luka-dev/playwright-php", 3 | "description": "A PHP Module, that help with generating of task script for playwright and send it node.js", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "luka-dev", 9 | "email": "leandr1999@gmail.com" 10 | } 11 | ], 12 | "autoload": { 13 | "classmap": [ 14 | "src" 15 | ] 16 | }, 17 | "require": { 18 | "php": ">=7.3", 19 | "ext-json": "*", 20 | "php-curl-class/php-curl-class": "8.* || 9.*", 21 | "ext-curl": "*" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^9.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ConnectionConfig.php: -------------------------------------------------------------------------------- 1 | address = $address; 24 | $this->port = $port; 25 | $this->authKey = $authKey; 26 | } 27 | 28 | public function inAuthNeeded(): bool 29 | { 30 | return $this->authKey !== null; 31 | } 32 | 33 | public function getAuthKey(): ?string 34 | { 35 | return $this->authKey; 36 | } 37 | 38 | public function getConnectionAddress(): string 39 | { 40 | return "$this->address:$this->port"; 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/ContextConfig.php: -------------------------------------------------------------------------------- 1 | false,//bool 9 | 'bypassCSP' => null, //bool 10 | 'deviceScaleFactor' => null, //int 11 | 'extraHTTPHeaders' => null, //object 12 | 'geolocation' => null, //object 13 | 'hasTouch' => null, //bool 14 | 'httpCredentials' => null, //bool 15 | 'ignoreHTTPSErrors' => true, //bool 16 | 'isMobile' => null, //bool 17 | 'javaScriptEnabled' => true, 18 | 'locale' => null, //string 19 | 'offline' => null, //bool 20 | 'permissions' => null, //array of string, 21 | 'proxy' => null, //object 22 | 'userAgent' => null, //string 23 | 'viewport' => null, //object 24 | ]; 25 | 26 | public function setAcceptDownloads(?bool $acceptDownloads) 27 | { 28 | $this->options['acceptDownloads'] = $acceptDownloads; 29 | } 30 | 31 | public function setBypassCsp(?bool $bypassCSP) 32 | { 33 | $this->options['bypassCSP'] = $bypassCSP; 34 | } 35 | 36 | public function setDeviceScaleFactor(?int $deviceScaleFactor) 37 | { 38 | $this->options['deviceScaleFactor'] = $deviceScaleFactor; 39 | } 40 | 41 | /** 42 | * @param null|array $extraHTTPHeaders array of headers, key -> header name, value -> header body 43 | */ 44 | public function setExtraHTTPHeaders(?array $extraHTTPHeaders) 45 | { 46 | if (is_array($extraHTTPHeaders)) { 47 | $this->options['extraHTTPHeaders'] = []; 48 | } else { 49 | $this->options['extraHTTPHeaders'] = null; 50 | } 51 | } 52 | 53 | public function addExtraHTTPHeader(string $name, string $value) 54 | { 55 | if (!is_array($this->options['extraHTTPHeaders'])) { 56 | $this->options['extraHTTPHeaders'] = []; 57 | } 58 | 59 | $this->options['extraHTTPHeaders'][$name] = $value; 60 | } 61 | 62 | public function setGeolocation(?float $latitude, ?float $longitude, int $accuracy = 0) 63 | { 64 | $this->options['geolocation'] = null; 65 | 66 | if (is_float($latitude) && is_float($longitude)) { 67 | $this->options['geolocation']['latitude'] = $latitude; 68 | $this->options['geolocation']['longitude'] = $longitude; 69 | $this->options['geolocation']['accuracy'] = $accuracy; 70 | } 71 | } 72 | 73 | public function hasTouch(?bool $hasTouch) 74 | { 75 | $this->options['hasTouch'] = $hasTouch; 76 | } 77 | 78 | public function setHttpCredentials(?string $username = null, ?string $password = null) 79 | { 80 | $this->options['httpCredentials'] = null; 81 | 82 | if (is_string($username) && is_string($password)) { 83 | $this->options['httpCredentials']['username'] = $username; 84 | $this->options['httpCredentials']['password'] = $password; 85 | } 86 | } 87 | 88 | public function setIgnoreHTTPSErrors(?bool $ignoreHTTPSErrors = null) 89 | { 90 | $this->options['ignoreHTTPSErrors'] = $ignoreHTTPSErrors; 91 | } 92 | 93 | public function isMobile(?bool $isMobile = null) 94 | { 95 | $this->options['isMobile'] = $isMobile; 96 | } 97 | 98 | public function setJavaScriptEnabled(?bool $javaScriptEnabled = null) 99 | { 100 | $this->options['javaScriptEnabled'] = $javaScriptEnabled; 101 | } 102 | 103 | public function setLocale(?string $locale = null) 104 | { 105 | $this->options['locale'] = $locale; 106 | } 107 | 108 | public function setOffline(?bool $offline = null) 109 | { 110 | $this->options['offline'] = $offline; 111 | } 112 | 113 | public function setPermissions(?array $permissions = null) 114 | { 115 | if (is_array($this->options['permissions'])) { 116 | $this->options['permissions'] = []; 117 | 118 | $this->options['permissions'] = array_values($permissions); 119 | } else { 120 | $this->options['permissions'] = null; 121 | } 122 | } 123 | 124 | public function setProxy(?string $server = null, ?string $bypass = null, ?string $username = null, ?string $password = null) 125 | { 126 | if (is_string($server)) { 127 | $this->options['proxy'] = [ 128 | 'server' => $server, 129 | 'bypass' => $bypass ?? '', 130 | 'username' => $username ?? '', 131 | 'password' => $password ?? '', 132 | ]; 133 | } else { 134 | $this->options['proxy'] = null; 135 | } 136 | } 137 | 138 | public function setUserAgent(?string $userAgent = null) 139 | { 140 | $this->options['userAgent'] = $userAgent; 141 | } 142 | 143 | public function setViewport(?int $width = null, ?int $height = null) 144 | { 145 | if (is_int($width) && is_int($height)) { 146 | $this->options['viewport']['width'] = $width; 147 | $this->options['viewport']['height'] = $height; 148 | } else { 149 | $this->options['viewport'] = null; 150 | } 151 | } 152 | 153 | public function toArray(): array 154 | { 155 | $options = []; 156 | 157 | foreach ($this->options as $key => $option) { 158 | if (!is_null($option)) { 159 | $options[$key] = $option; 160 | } 161 | } 162 | 163 | return $options; 164 | } 165 | } -------------------------------------------------------------------------------- /src/Exception/ConnectionError.php: -------------------------------------------------------------------------------- 1 | $name, 45 | 'value' => $value, 46 | 'url' => $url, 47 | 'domain' => $domain, 48 | 'path' => $path, 49 | 'expires' => $expires, 50 | 'httpOnly' => $httpOnly, 51 | 'secure' => $secure, 52 | 'sameSite' => $sameSite, 53 | ]; 54 | 55 | $this->merge(Functions::callAwait("$this->contextName.addCookies", [$data])); 56 | 57 | return $this; 58 | } 59 | 60 | public function getContextVarName(): string { 61 | return $this->contextName; 62 | } 63 | 64 | public function clearCookies(): Context 65 | { 66 | $this->merge(Functions::callAwait("$this->contextName.clearCookies")); 67 | 68 | return $this; 69 | } 70 | 71 | public function clearPermissions(): Context 72 | { 73 | $this->merge(Functions::callAwait("$this->contextName.clearPermissions")); 74 | 75 | return $this; 76 | } 77 | 78 | public function close(): Context 79 | { 80 | $this->merge(Functions::callAwait("$this->contextName.close")); 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Get cookies array, saved to var by urls list 87 | * 88 | * @param string $varToSave 89 | * @param string[] $urls 90 | * @return $this 91 | */ 92 | public function cookiesToVar(string $varToSave, array $urls): Context 93 | { 94 | $builder = Functions::callAwait("$this->contextName.cookies", $urls); 95 | $builder->toVar($varToSave); 96 | 97 | $this->merge($builder); 98 | 99 | return $this; 100 | } 101 | 102 | 103 | /** 104 | * @param string[] $permissions 105 | * '*' 106 | * 'geolocation' 107 | * 'midi' 108 | * 'midi-sysex' (system-exclusive midi) 109 | * 'notifications' 110 | * 'push' 111 | * 'camera' 112 | * 'microphone 113 | * 'background-sync' 114 | * 'ambient-light-sensor' 115 | * 'accelerometer' 116 | * 'gyroscope' 117 | * 'magnetometer' 118 | * 'accessibility-events' 119 | * 'clipboard-read' 120 | * 'clipboard-write' 121 | * 'payment-handler' 122 | * @param string $origin url 123 | * @return Context 124 | */ 125 | public function grantPermissions(array $permissions, string $origin): Context 126 | { 127 | $options = [ 128 | 'origin' => $origin, 129 | ]; 130 | 131 | $this->merge(Functions::callAwait("$this->contextName.grantPermissions", $permissions, $options)); 132 | 133 | 134 | return $this; 135 | } 136 | 137 | public function newPage(): Page 138 | { 139 | $customVarName = 'page' . Vars::generateRandomVarName(); 140 | 141 | $builder = Functions::callAwait("$this->contextName.newPage"); 142 | $builder->toVar($customVarName); 143 | 144 | $this->merge($builder); 145 | 146 | return new Page($customVarName, $this->jsString, $this->requestTimeout); 147 | } 148 | 149 | /** 150 | * @param int $index 151 | * @return Page 152 | */ 153 | public function pageByIndex(int $index): Page 154 | { 155 | $customVarName = 'page' . Vars::generateRandomVarName(); 156 | 157 | $builder = Functions::callAwait("$this->contextName.page"); 158 | $builder->index($index)->toVar($customVarName); 159 | return new Page($customVarName, $this->jsString, $this->requestTimeout); 160 | } 161 | 162 | /** 163 | * @param string $rule `**\/*.{png,jpg,jpeg}` or /(\.png$)|(\.jpg$)/ 164 | * @return $this 165 | */ 166 | public function routeAbort(string $rule): Context 167 | { 168 | $builder = Functions::callAwait("$this->contextName.route", $rule, 'route => route.abort()'); 169 | $this->merge($builder); 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * @param string $rule `**\/*.{png,jpg,jpeg}` or /(\.png$)|(\.jpg$)/ 176 | * @return $this 177 | */ 178 | public function unroute(string $rule): Context 179 | { 180 | $this->merge(Functions::callAwait("$this->contextName.unroute", $rule)); 181 | 182 | return $this; 183 | } 184 | 185 | public function setGeolocation(float $latitude, float $longitude, int $accuracy = 0): Context 186 | { 187 | $data = [ 188 | 'latitude' => $latitude, 189 | 'longitude' => $longitude, 190 | 'accuracy' => $accuracy, 191 | ]; 192 | 193 | $this->merge(Functions::callAwait("$this->contextName.setGeolocation", $data)); 194 | 195 | return $this; 196 | } 197 | 198 | public function setHTTPCredentials(string $username, string $password): Context 199 | { 200 | $data = [ 201 | 'username' => $username, 202 | 'password' => $password, 203 | ]; 204 | 205 | $this->merge(Functions::callAwait("$this->contextName.setHTTPCredentials", $data)); 206 | return $this; 207 | } 208 | 209 | public function setOffline(bool $offline): Context 210 | { 211 | $this->merge(Functions::callAwait("$this->contextName.setOffline", $offline)); 212 | return $this; 213 | } 214 | 215 | /** 216 | * @param string $event "close" or "page" 217 | * @param int $timeout milliseconds - 0 to disable 218 | * @return $this 219 | */ 220 | public function waitForEvent(string $event, int $timeout = 30000): Context 221 | { 222 | 223 | $data = [ 224 | 'timeout' => $timeout, 225 | ]; 226 | 227 | $this->merge(Functions::callAwait("$this->contextName.waitForEvent", $event, $data)); 228 | 229 | return $this; 230 | } 231 | 232 | 233 | } 234 | -------------------------------------------------------------------------------- /src/Js/Api/ElementHandle.php: -------------------------------------------------------------------------------- 1 | elementVarName = $elementVarName; 20 | parent::__construct($customJsStore,$requestTimeout); 21 | } 22 | 23 | public function getElementVarName(): string 24 | { 25 | return $this->elementVarName; 26 | } 27 | 28 | /** 29 | * @param string $selector 30 | * @param string $button left|right|middle 31 | * @param int $clickCount 32 | * @param int $delay btw clicks in milliseconds 33 | * @param array $modifiers ["Alt"|"Control"|"Meta"|"Shift"] or empty 34 | * @param bool $force Whether to bypass the actionability checks 35 | * @param int $timeout 36 | */ 37 | public function click(string $selector, string $button = 'left', int $clickCount = 1, int $delay = 0, array $modifiers = [], bool $force = false, int $timeout = 30000): void 38 | { 39 | $options = [ 40 | 'button' => $button, 41 | 'clickCount' => $clickCount, 42 | 'delay' => $delay, 43 | 'modifiers' => $modifiers, 44 | 'force' => $force, 45 | 'timeout' => $timeout, 46 | ]; 47 | 48 | $builder = Functions::callAwait("$this->elementVarName.click", $selector, $options); 49 | $this->merge($builder); 50 | } 51 | 52 | /** 53 | * @param string $selector 54 | * @param int $timeout 55 | */ 56 | public function uncheck(string $selector, int $timeout = 30000): void 57 | { 58 | 59 | $options = [ 60 | 'timeout' => $timeout, 61 | ]; 62 | 63 | $this->addToTimeout($timeout); 64 | 65 | $builder = Functions::callAwait("$this->elementVarName.uncheck", $selector, $options); 66 | $this->merge($builder); 67 | } 68 | 69 | /** 70 | * @param string $selector 71 | * @param int $timeout 72 | */ 73 | public function check(string $selector, int $timeout = 30000): void 74 | { 75 | 76 | $options = [ 77 | 'timeout' => $timeout, 78 | ]; 79 | 80 | $this->addToTimeout($timeout); 81 | 82 | $builder = Functions::callAwait("$this->elementVarName.check", $selector, $options); 83 | $this->merge($builder); 84 | } 85 | 86 | /** 87 | * Double click 88 | * 89 | * @param string $selector 90 | * @param string $button left|right|middle 91 | * @param int $delay btw clicks in milliseconds 92 | * @param array $modifiers ["Alt"|"Control"|"Meta"|"Shift"] or empty 93 | * @param bool $force Whether to bypass the actionability checks 94 | * @param int $timeout 95 | */ 96 | public function dblclick(string $selector, string $button = 'left', int $delay = 0, array $modifiers = [], bool $force = false, int $timeout = 30000): void 97 | { 98 | $options = [ 99 | 'button' => $button, 100 | 'delay' => $delay, 101 | 'modifiers' => $modifiers, 102 | 'force' => $force, 103 | 'timeout' => $timeout, 104 | ]; 105 | 106 | $this->addToTimeout($timeout); 107 | 108 | $builder = Functions::callAwait("$this->elementVarName.dblclick", $selector, $options); 109 | $this->merge($builder); 110 | } 111 | 112 | public function fill(string $selector, string $value, int $timeout = 30000): void 113 | { 114 | $options = [ 115 | 'timeout' => $timeout, 116 | ]; 117 | 118 | $this->addToTimeout($timeout); 119 | 120 | 121 | $builder = Functions::callAwait("$this->elementVarName.fill", $selector, $value, $options); 122 | $this->merge($builder); 123 | } 124 | 125 | public function focus(string $selector, int $timeout = 30000): void 126 | { 127 | $options = [ 128 | 'timeout' => $timeout, 129 | ]; 130 | 131 | $this->addToTimeout($timeout); 132 | 133 | 134 | $builder = Functions::callAwait("$this->elementVarName.focus", $selector, $options); 135 | $this->merge($builder); 136 | } 137 | 138 | /** 139 | * @param string $selector 140 | * @param string $name 141 | * @param string $varName 142 | */ 143 | public function getAttributeToVar(string $selector, string $name, string $varName): void 144 | { 145 | $builder = Functions::callAwait("$this->elementVarName.getAttribute", $selector, $name); 146 | $builder->toVar($varName); 147 | 148 | $this->merge($builder); 149 | } 150 | 151 | /** 152 | * @param string $selector 153 | * @param string[] $modifiers > 154 | * @param bool $force 155 | * @param int $timeout 156 | */ 157 | public function hover(string $selector, array $modifiers = [], bool $force = false, int $timeout = 30000): void 158 | { 159 | 160 | $options = [ 161 | 'modifiers' => $modifiers, 162 | 'force' => $force, 163 | 'timeout' => $timeout, 164 | ]; 165 | 166 | $this->addToTimeout($timeout); 167 | 168 | $builder = Functions::callAwait("$this->elementVarName.hover", $selector, $options); 169 | $this->merge($builder); 170 | } 171 | 172 | /** 173 | * Focuses the element, and then uses keyboard.down and keyboard.up. 174 | * @param string $key ArrowLeft or a 175 | * @param int $delay 176 | * @param int $timeout 177 | */ 178 | public function press(string $key, int $delay = 0, int $timeout = 30000): void 179 | { 180 | $options = [ 181 | 'delay' => $delay, 182 | 'timeout' => $timeout, 183 | ]; 184 | 185 | $this->addToTimeout($timeout); 186 | 187 | $builder = Functions::callAwait("$this->elementVarName.press", $key, $options); 188 | $this->merge($builder); 189 | } 190 | 191 | 192 | public function scrollIntoViewIfNeeded(int $timeout = 30000): void 193 | { 194 | $options = [ 195 | 'timeout' => $timeout, 196 | ]; 197 | 198 | $this->addToTimeout($timeout); 199 | 200 | $builder = Functions::callAwait("$this->elementVarName.scrollIntoViewIfNeeded", $options); 201 | $this->merge($builder); 202 | } 203 | 204 | /** 205 | * @param string $selector 206 | * @param SelectOption $value 207 | */ 208 | public function selectOption(string $selector, SelectOption $value): void 209 | { 210 | $this->selectOptions($selector, [$value]); 211 | } 212 | 213 | /** 214 | * @param string $selector 215 | * @param SelectOption[] $values 216 | */ 217 | public function selectOptions(string $selector, array $values): void 218 | { 219 | 220 | $valuesSerialized = []; 221 | 222 | foreach ($values as $value) { 223 | $valuesSerialized[] = $value->toArray(); 224 | } 225 | 226 | $builder = Functions::callAwait("$this->elementVarName.selectOption", $selector, ...$valuesSerialized); 227 | $this->merge($builder); 228 | } 229 | 230 | public function selectText(int $timeout = 30000): void 231 | { 232 | $options = [ 233 | 'timeout' => $timeout, 234 | ]; 235 | 236 | $this->addToTimeout($timeout); 237 | 238 | $builder = Functions::callAwait("$this->elementVarName.selectText", $options); 239 | $this->merge($builder); 240 | } 241 | 242 | /** 243 | * @param string $varName 244 | */ 245 | public function textContentToVar(string $varName): void 246 | { 247 | $builder = Functions::callAwait("$this->elementVarName.textContent"); 248 | $builder->toVar($varName); 249 | $this->merge($builder); 250 | } 251 | 252 | /** 253 | * @param string $text 254 | * @param int $delay 255 | * @param int $timeout 256 | */ 257 | public function type(string $text, int $delay = 0, int $timeout = 30000): void 258 | { 259 | $options = [ 260 | 'delay' => $delay, 261 | 'timeout' => $timeout, 262 | ]; 263 | 264 | $this->addToTimeout($timeout); 265 | 266 | $builder = Functions::callAwait("$this->elementVarName.type", $text, $options); 267 | $this->merge($builder); 268 | } 269 | 270 | /** 271 | * @param string $selector 272 | * @param string $customVarName 273 | * @param int $timeout 274 | */ 275 | public function innerHTMLToVar(string $selector, string $customVarName, int $timeout = 30000): void 276 | { 277 | 278 | $options = [ 279 | 'timeout' => $timeout, 280 | ]; 281 | 282 | $this->addToTimeout($timeout); 283 | 284 | $builder = Functions::callAwait("$this->elementVarName.innerHTML", $selector, $options); 285 | $builder->toVar($customVarName); 286 | $this->merge($builder); 287 | } 288 | 289 | /** 290 | * @param string $selector 291 | * @param string $customVarName 292 | * @param int $timeout 293 | */ 294 | public function innerTextToVar(string $selector, string $customVarName, int $timeout = 30000): void 295 | { 296 | 297 | $options = [ 298 | 'timeout' => $timeout, 299 | ]; 300 | 301 | $this->addToTimeout($timeout); 302 | 303 | $builder = Functions::callAwait("$this->elementVarName.innerText", $selector, $options); 304 | $builder->toVar($customVarName); 305 | $this->merge($builder); 306 | } 307 | 308 | public function boundingBox(): BoundingBox 309 | { 310 | $customVarName = 'boundingBox' . Vars::generateRandomVarName(); 311 | 312 | $builder = Functions::callAwait("$this->elementVarName.boundingBox"); 313 | $builder->toVar($customVarName); 314 | $this->merge($builder); 315 | 316 | return new BoundingBox($customVarName, $this->jsString, $this->requestTimeout); 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /src/Js/Api/Frame.php: -------------------------------------------------------------------------------- 1 | frameVarName = $frameVarName; 16 | parent::__construct($customJsStore, $requestTimeout); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Js/Api/Page.php: -------------------------------------------------------------------------------- 1 | pageVarName = $pageVarName; 24 | parent::__construct($customJsStore, $requestTimeout); 25 | } 26 | 27 | public function getPageVarName(): string 28 | { 29 | return $this->pageVarName; 30 | } 31 | 32 | /** 33 | * On runtime can fail if element not founded. Use try catch 34 | * a.k.a. selectOne 35 | * 36 | * @param string $selector 37 | * @return ElementHandle 38 | */ 39 | public function query(string $selector): ElementHandle 40 | { 41 | $customVarName = 'element' . Vars::generateRandomVarName(); 42 | 43 | $builder = Functions::callAwait("$this->pageVarName.$", $selector); 44 | $builder->toVar($customVarName); 45 | $this->merge($builder); 46 | 47 | return new ElementHandle($customVarName, $this->jsString, $this->requestTimeout); 48 | } 49 | 50 | // /** 51 | // * @param string $selector 52 | // * @param string $varName 53 | // */ 54 | // public function queryAllToVar(string $selector, string $varName): void 55 | // { 56 | // //todo rework array stuff 57 | // 58 | // $builder = Functions::callAwait("$this->pageVarName.$$", $selector); 59 | // $builder->toVar($varName); 60 | // $this->merge($builder); 61 | // } 62 | 63 | /** 64 | * @param string $selector 65 | * @param string $button left|right|middle 66 | * @param int $clickCount 67 | * @param int $delay btw clicks in milliseconds 68 | * @param array $modifiers ["Alt"|"Control"|"Meta"|"Shift"] or empty 69 | * @param bool $force Whether to bypass the actionability checks 70 | * @param int $timeout 71 | */ 72 | public function click(string $selector, string $button = 'left', int $clickCount = 1, int $delay = 0, array $modifiers = [], bool $force = false, int $timeout = 30000): void 73 | { 74 | $options = [ 75 | 'button' => $button, 76 | 'clickCount' => $clickCount, 77 | 'delay' => $delay, 78 | 'modifiers' => $modifiers, 79 | 'force' => $force, 80 | 'timeout' => $timeout, 81 | ]; 82 | 83 | $this->addToTimeout($timeout); 84 | 85 | $builder = Functions::callAwait("$this->pageVarName.click", $selector, $options); 86 | $this->merge($builder); 87 | } 88 | 89 | public function clickAsync(string $selector, string $button = 'left', int $clickCount = 1, int $delay = 0, array $modifiers = [], bool $force = false, int $timeout = 30000): void 90 | { 91 | $options = [ 92 | 'button' => $button, 93 | 'clickCount' => $clickCount, 94 | 'delay' => $delay, 95 | 'modifiers' => $modifiers, 96 | 'force' => $force, 97 | 'timeout' => $timeout, 98 | ]; 99 | 100 | $this->addToTimeout($timeout); 101 | 102 | $builder = Functions::call("$this->pageVarName.click", $selector, $options); 103 | $this->merge($builder); 104 | } 105 | 106 | /** 107 | * @param string $selector 108 | * @param string $key F1 - F12, Digit0- Digit9, KeyA- KeyZ, Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp, etc. 109 | * @param int $timeout 110 | */ 111 | public function press(string $selector, string $key, int $timeout = 30000): void 112 | { 113 | $options = [ 114 | 'timeout' => $timeout, 115 | ]; 116 | 117 | $this->addToTimeout($timeout); 118 | 119 | $builder = Functions::callAwait("$this->pageVarName.press", $selector, $key, $options); 120 | $this->merge($builder); 121 | } 122 | 123 | public function pressAsync(string $selector, string $key, int $timeout = 30000): void 124 | { 125 | $options = [ 126 | 'timeout' => $timeout, 127 | ]; 128 | 129 | $this->addToTimeout($timeout); 130 | 131 | $builder = Functions::call("$this->pageVarName.press", $selector, $key, $options); 132 | $this->merge($builder); 133 | } 134 | 135 | public function close(): void 136 | { 137 | $builder = Functions::callAwait("$this->pageVarName.close"); 138 | $this->merge($builder); 139 | } 140 | 141 | public function contentToVar(string $varName): void 142 | { 143 | $builder = Functions::callAwait("$this->pageVarName.content"); 144 | $builder->toVar($varName); 145 | $this->merge($builder); 146 | } 147 | 148 | /** 149 | * Double click 150 | * 151 | * @param string $selector 152 | * @param string $button left|right|middle 153 | * @param int $delay btw clicks in milliseconds 154 | * @param array $modifiers ["Alt"|"Control"|"Meta"|"Shift"] or empty 155 | * @param bool $force Whether to bypass the actionability checks 156 | * @param int $timeout 157 | */ 158 | public function dblclick(string $selector, string $button = 'left', int $delay = 0, array $modifiers = [], bool $force = false, int $timeout = 30000): void 159 | { 160 | $options = [ 161 | 'button' => $button, 162 | 'delay' => $delay, 163 | 'modifiers' => $modifiers, 164 | 'force' => $force, 165 | 'timeout' => $timeout, 166 | ]; 167 | 168 | $this->addToTimeout($timeout); 169 | 170 | $builder = Functions::callAwait("$this->pageVarName.dblclick", $selector, $options); 171 | $this->merge($builder); 172 | } 173 | 174 | /** 175 | * @param string|null $media null|"screen"|"print" 176 | * @param string|null $colorScheme null|"light"|"dark"|"no-preference" 177 | */ 178 | public function emulateMedia(?string $media = 'screen', ?string $colorScheme = 'screen'): void 179 | { 180 | 181 | $options = [ 182 | 'media' => $media, 183 | 'colorScheme' => $colorScheme, 184 | ]; 185 | 186 | $builder = Functions::callAwait("$this->pageVarName.dblclick", $options); 187 | $this->merge($builder); 188 | } 189 | 190 | /** 191 | * exec script in page context 192 | * @param Script $script 193 | */ 194 | public function evaluate(Script $script): void 195 | { 196 | $builder = Functions::callAwait("$this->pageVarName.evaluate",$script->getJs()); 197 | 198 | $this->merge($builder); 199 | } 200 | 201 | public function evaluateAsync(Script $script): void 202 | { 203 | $builder = Functions::call("$this->pageVarName.evaluate",$script->getJs()); 204 | 205 | $this->merge($builder); 206 | } 207 | 208 | /** 209 | * 210 | * exec script in page context and return data to var 211 | * script should contain return 212 | * 213 | * @param Script $script 214 | * @param string $varName 215 | */ 216 | public function evaluateToVar(Script $script, string $varName): void 217 | { 218 | $builder = Functions::callAwait("$this->pageVarName.evaluate", $script->getJs()); 219 | $builder->toVar($varName); 220 | 221 | $this->merge($builder); 222 | } 223 | 224 | public function fill(string $selector, string $value, int $timeout = 30000): void 225 | { 226 | $options = [ 227 | 'timeout' => $timeout, 228 | ]; 229 | 230 | $this->addToTimeout($timeout); 231 | 232 | $builder = Functions::callAwait("$this->pageVarName.fill", $selector, $value, $options); 233 | $this->merge($builder); 234 | } 235 | 236 | public function focus(string $selector, int $timeout = 30000): void 237 | { 238 | $options = [ 239 | 'timeout' => $timeout, 240 | ]; 241 | 242 | $this->addToTimeout($timeout); 243 | 244 | $builder = Functions::callAwait("$this->pageVarName.focus", $selector, $options); 245 | $this->merge($builder); 246 | } 247 | 248 | /** 249 | * Should be at least one argument. 250 | * 251 | * @param string|null $name 252 | * @param string|null $url 253 | * @return Frame 254 | * @throws Exception 255 | */ 256 | public function frame(string $name = null, string $url = null): Frame 257 | { 258 | if ($name === null && $url === null) { 259 | throw new Exception('Should be at least one argument.'); 260 | } 261 | 262 | $args = $name; 263 | 264 | if ($url !== null) { 265 | $args = [ 266 | 'url' => $url, 267 | ]; 268 | } 269 | 270 | if ($name !== null) { 271 | $args['name'] = $name; 272 | } 273 | 274 | $frameVar = 'frame' . Vars::generateRandomVarName(); 275 | 276 | $builder = Functions::call("$this->pageVarName.frame", $args); 277 | $builder->toVar($frameVar); 278 | $this->merge($builder); 279 | 280 | return new Frame($frameVar, $this->jsString, $this->requestTimeout); 281 | } 282 | 283 | //todo add "frames" 284 | 285 | /** 286 | * @param string $selector 287 | * @param string $name 288 | * @param string $varName 289 | */ 290 | public function getAttributeToVar(string $selector, string $name, string $varName): void 291 | { 292 | $builder = Functions::callAwait("$this->pageVarName.getAttribute", $selector, $name); 293 | $builder->toVar($varName); 294 | 295 | $this->merge($builder); 296 | } 297 | 298 | /** 299 | * @param string $waitUntil "load"|"domcontentloaded"|"networkidle" 300 | * @param int $timeout 301 | */ 302 | public function goBack(string $waitUntil = 'load', int $timeout = 30000): void 303 | { 304 | $options = [ 305 | 'timeout' => $timeout, 306 | 'waitUntil' => $waitUntil, 307 | ]; 308 | 309 | $this->addToTimeout($timeout); 310 | 311 | $builder = Functions::callAwait("$this->pageVarName.goBack", $options); 312 | $this->merge($builder); 313 | } 314 | 315 | /** 316 | * @param string $waitUntil "load"|"domcontentloaded"|"networkidle" 317 | * @param int $timeout 318 | */ 319 | public function goForward(string $waitUntil = 'load', int $timeout = 30000): void 320 | { 321 | $options = [ 322 | 'timeout' => $timeout, 323 | 'waitUntil' => $waitUntil, 324 | ]; 325 | 326 | $this->addToTimeout($timeout); 327 | 328 | $builder = Functions::callAwait("$this->pageVarName.goForward", $options); 329 | $this->merge($builder); 330 | } 331 | 332 | /** 333 | * @param string $url 334 | * @param string|null $referer 335 | * @param string $waitUntil "load"|"domcontentloaded"|"networkidle" 336 | * @param int $timeout 337 | */ 338 | public function goto(string $url, string $referer = null, string $waitUntil = 'domcontentloaded', int $timeout = 30000): void 339 | { 340 | $options = [ 341 | 'timeout' => $timeout, 342 | 'waitUntil' => $waitUntil, 343 | ]; 344 | 345 | if ($referer !== null) { 346 | $options['referer'] = $referer; 347 | } 348 | 349 | $this->addToTimeout($timeout); 350 | 351 | $builder = Functions::callAwait("$this->pageVarName.goto", $url, $options); 352 | $this->merge($builder); 353 | } 354 | 355 | /** 356 | * @param string $selector 357 | * @param string[] $modifiers > 358 | * @param bool $force 359 | * @param int $timeout 360 | */ 361 | public function hover(string $selector, array $modifiers = [], bool $force = false, int $timeout = 30000): void 362 | { 363 | 364 | $options = [ 365 | 'modifiers' => $modifiers, 366 | 'force' => $force, 367 | 'timeout' => $timeout, 368 | ]; 369 | 370 | $this->addToTimeout($timeout); 371 | 372 | $builder = Functions::callAwait("$this->pageVarName.hover", $selector, $options); 373 | $this->merge($builder); 374 | } 375 | 376 | /** 377 | * @param string $selector 378 | * @param string $customVarName 379 | * @param int $timeout 380 | */ 381 | public function innerHTMLToVar(string $selector, string $customVarName, int $timeout = 30000): void 382 | { 383 | 384 | $options = [ 385 | 'timeout' => $timeout, 386 | ]; 387 | 388 | $this->addToTimeout($timeout); 389 | 390 | $builder = Functions::callAwait("$this->pageVarName.innerHTML", $selector, $options); 391 | $builder->toVar($customVarName); 392 | $this->merge($builder); 393 | } 394 | 395 | /** 396 | * @param string $selector 397 | * @param string $customVarName 398 | * @param int $timeout 399 | */ 400 | public function innerTextToVar(string $selector, string $customVarName, int $timeout = 30000): void 401 | { 402 | 403 | $options = [ 404 | 'timeout' => $timeout, 405 | ]; 406 | 407 | $this->addToTimeout($timeout); 408 | 409 | $builder = Functions::callAwait("$this->pageVarName.innerText", $selector, $options); 410 | $builder->toVar($customVarName); 411 | $this->merge($builder); 412 | } 413 | 414 | public function isClosedToVar(string $customVarName): void 415 | { 416 | $builder = Functions::call("$this->pageVarName.isClosed"); 417 | $builder->toVar($customVarName); 418 | $this->merge($builder); 419 | } 420 | 421 | public function opener(): Page 422 | { 423 | $customVarName = 'page' . Vars::generateRandomVarName(); 424 | 425 | $builder = Functions::callAwait("$this->pageVarName.opener"); 426 | $builder->toVar($customVarName); 427 | 428 | $this->merge($builder); 429 | 430 | return new Page($customVarName, $this->jsString, $this->requestTimeout); 431 | } 432 | 433 | /** 434 | * @param string $waitUntil "load"|"domcontentloaded"|"networkidle" 435 | * @param int $timeout 436 | */ 437 | public function reload(string $waitUntil = 'load', int $timeout = 30000): void 438 | { 439 | $options = [ 440 | 'timeout' => $timeout, 441 | 'waitUntil' => $waitUntil, 442 | ]; 443 | 444 | $this->addToTimeout($timeout); 445 | 446 | $builder = Functions::callAwait("$this->pageVarName.reload", $options); 447 | $this->merge($builder); 448 | } 449 | 450 | /** 451 | * @param array|string[] $resourceTypes 452 | * @param array $allowedHosts 453 | * @param array $pathToBlock ['/example/path.jpg']; no get params 454 | */ 455 | public function blockRequests(array $resourceTypes = ['image', 'stylesheet'], array $allowedHosts = [], array $pathToBlock = []): void 456 | { 457 | $script = new Script(); 458 | 459 | $resVarName = 'resourceTypes' . Vars::generateRandomVarName(); 460 | $resTypes = new Builder(); 461 | $resTypes->append(Constructions::build($resourceTypes), false, false); 462 | $resTypes->toVar($resVarName); 463 | $script->append($resTypes->getJs()); 464 | 465 | $hostsVarName = 'allowedHosts' . Vars::generateRandomVarName(); 466 | $hostsArr = new Builder(); 467 | $hostsArr->append(Constructions::build($allowedHosts), false, false); 468 | $hostsArr->toVar($hostsVarName); 469 | $script->append($hostsArr->getJs(), true, false); 470 | 471 | $urlsToBlockVarName = 'pathToBlock' . Vars::generateRandomVarName(); 472 | $urlsToBlockObj = new Builder(); 473 | $urlsToBlockObj->append(Constructions::build($pathToBlock), false, false); 474 | $urlsToBlockObj->toVar($urlsToBlockVarName); 475 | $script->append($urlsToBlockObj->getJs(), true, false); 476 | 477 | $pageVarName = $this->pageVarName; 478 | 479 | $script->append(<< { 481 | let url = route.request().url(); 482 | let pathname = modules.URL.parse(url).pathname; 483 | let hostname = modules.URL.parse(url).hostname; 484 | let doctype = route.request().resourceType(); 485 | 486 | let needAbort = false; 487 | 488 | if ($resVarName.includes(doctype)) { 489 | needAbort = true; 490 | } 491 | 492 | if (!needAbort && $hostsVarName.length && !$hostsVarName.includes(hostname)) { 493 | needAbort = true; 494 | } 495 | 496 | if (!needAbort && $urlsToBlockVarName.includes(pathname)) { 497 | needAbort = true; 498 | } 499 | 500 | if (needAbort) { 501 | route.abort('aborted'); 502 | } else { 503 | route.continue(); 504 | } 505 | }); 506 | JS 507 | ); 508 | 509 | $this->merge($script); 510 | } 511 | 512 | /** 513 | * @param string $selector 514 | * @param SelectOption $value 515 | */ 516 | public function selectOption(string $selector, SelectOption $value): void 517 | { 518 | $this->selectOptions($selector, [$value]); 519 | } 520 | 521 | /** 522 | * @param string $selector 523 | * @param SelectOption[] $values 524 | */ 525 | public function selectOptions(string $selector, array $values): void 526 | { 527 | 528 | $valuesSerialized = []; 529 | 530 | foreach ($values as $value) { 531 | $valuesSerialized[] = $value->toArray(); 532 | } 533 | 534 | $builder = Functions::callAwait("$this->pageVarName.selectOption", $selector, ...$valuesSerialized); 535 | $this->merge($builder); 536 | } 537 | 538 | /** 539 | * @param Header[] $headers 540 | */ 541 | public function setExtraHTTPHeaders(array $headers): void 542 | { 543 | 544 | $headersSerialized = []; 545 | 546 | foreach ($headers as $header) { 547 | $headersSerialized[] = $header->toArray(); 548 | } 549 | 550 | $builder = Functions::callAwait("$this->pageVarName.setExtraHTTPHeaders", $headersSerialized); 551 | $this->merge($builder); 552 | } 553 | 554 | /** 555 | * @param int $width 556 | * @param int $height 557 | */ 558 | public function setViewportSize(int $width, int $height): void 559 | { 560 | 561 | $options = [ 562 | 'width' => $width, 563 | 'height' => $height, 564 | ]; 565 | 566 | $builder = Functions::callAwait("$this->pageVarName.setViewportSize", $options); 567 | $this->merge($builder); 568 | } 569 | 570 | /** 571 | * @param string $selector 572 | * @param string $varName 573 | * @param int $timeout 574 | */ 575 | public function textContentToVar(string $selector, string $varName, int $timeout = 30000): void 576 | { 577 | 578 | $options = [ 579 | 'timeout' => $timeout, 580 | ]; 581 | 582 | $this->addToTimeout($timeout); 583 | 584 | $builder = Functions::callAwait("$this->pageVarName.textContent", $selector, $options); 585 | $builder->toVar($varName); 586 | $this->merge($builder); 587 | } 588 | 589 | /** 590 | * @param string $varName 591 | */ 592 | public function titleToVar(string $varName): void 593 | { 594 | $builder = Functions::callAwait("$this->pageVarName.title"); 595 | $builder->toVar($varName); 596 | $this->merge($builder); 597 | } 598 | 599 | /** 600 | * @param string $selector 601 | * @param string $text 602 | * @param int $delay 603 | * @param int $timeout 604 | */ 605 | public function type(string $selector, string $text, int $delay = 0, int $timeout = 30000): void 606 | { 607 | 608 | $options = [ 609 | 'delay' => $delay, 610 | 'timeout' => $timeout, 611 | ]; 612 | 613 | $this->addToTimeout($timeout); 614 | 615 | $builder = Functions::callAwait("$this->pageVarName.type", $selector, $text, $options); 616 | $this->merge($builder); 617 | } 618 | 619 | /** 620 | * @param string $selector 621 | * @param int $timeout 622 | */ 623 | public function uncheck(string $selector, int $timeout = 30000): void 624 | { 625 | 626 | $options = [ 627 | 'timeout' => $timeout, 628 | ]; 629 | 630 | $this->addToTimeout($timeout); 631 | 632 | $builder = Functions::callAwait("$this->pageVarName.uncheck", $selector, $options); 633 | $this->merge($builder); 634 | } 635 | 636 | /** 637 | * @param string $selector 638 | * @param int $timeout 639 | */ 640 | public function check(string $selector, int $timeout = 30000): void 641 | { 642 | 643 | $options = [ 644 | 'timeout' => $timeout, 645 | ]; 646 | 647 | $this->addToTimeout($timeout); 648 | 649 | $builder = Functions::callAwait("$this->pageVarName.check", $selector, $options); 650 | $this->merge($builder); 651 | } 652 | 653 | /** 654 | * @param string $state <"load"|"domcontentloaded"|"networkidle"> 655 | * @param int $timeout 656 | */ 657 | public function waitForLoadState(string $state, int $timeout = 30000): void 658 | { 659 | 660 | $options = [ 661 | 'timeout' => $timeout, 662 | ]; 663 | 664 | $this->addToTimeout($timeout); 665 | 666 | $builder = Functions::callAwait("$this->pageVarName.waitForLoadState", $state, $options); 667 | $this->merge($builder); 668 | } 669 | 670 | /** 671 | * @param string $waitUntil <"load"|"domcontentloaded"|"networkidle"> 672 | * @param int $timeout 673 | */ 674 | public function waitForNavigation(string $waitUntil = 'load', int $timeout = 30000): void 675 | { 676 | 677 | $options = [ 678 | 'timeout' => $timeout, 679 | 'waitUntil' => $waitUntil, 680 | ]; 681 | 682 | $this->addToTimeout($timeout); 683 | 684 | $builder = Functions::callAwait("$this->pageVarName.waitForNavigation", $options); 685 | $this->merge($builder); 686 | } 687 | 688 | /** 689 | * @param string $selector 690 | * @param string $state <"attached"|"detached"|"visible"|"hidden"> 691 | * @param int $timeout 692 | */ 693 | public function waitForSelector(string $selector, string $state = 'visible', int $timeout = 30000): void 694 | { 695 | 696 | $options = [ 697 | 'timeout' => $timeout, 698 | 'state' => $state, 699 | ]; 700 | 701 | $this->addToTimeout($timeout); 702 | 703 | $builder = Functions::callAwait("$this->pageVarName.waitForSelector", $selector, $options); 704 | $this->merge($builder); 705 | } 706 | 707 | /** 708 | * @param string $button "left"|"right"|"middle" 709 | * @param int $clickCount 710 | */ 711 | public function mouseDown(string $button = 'left', int $clickCount = 1): void { 712 | $options = [ 713 | 'button' => $button, 714 | 'clickCount' => $clickCount, 715 | ]; 716 | $builder = Functions::callAwait("$this->pageVarName.mouse.down", $options); 717 | $this->merge($builder); 718 | } 719 | 720 | /** 721 | * @param string $button "left"|"right"|"middle" 722 | * @param int $clickCount 723 | */ 724 | public function mouseUp(string $button = 'left', int $clickCount = 1): void { 725 | $options = [ 726 | 'button' => $button, 727 | 'clickCount' => $clickCount, 728 | ]; 729 | $builder = Functions::callAwait("$this->pageVarName.mouse.up", $options); 730 | $this->merge($builder); 731 | } 732 | 733 | /** 734 | * @param string $x 735 | * @param string $y 736 | * @param int $steps 737 | */ 738 | public function mouseMove(string $x, string $y, int $steps = 1): void { 739 | $options = [ 740 | 'steps' => $steps, 741 | ]; 742 | 743 | $xNative = new NativeValue($x); 744 | $yNative = new NativeValue($y); 745 | 746 | $builder = Functions::callAwait("$this->pageVarName.mouse.move", $xNative, $yNative, $options); 747 | $this->merge($builder); 748 | } 749 | 750 | public function catchNewPage(): Page { 751 | $promise = new Script(); 752 | $promise->append("await Promise.all([context.waitForEvent('page')])"); 753 | $this->merge($promise); 754 | 755 | $varName = 'page'.Vars::generateRandomVarName(); 756 | 757 | $getLastPage = new Script(); 758 | $getLastPage->append("context.pages()[context.pages().length - 1]", false, false); 759 | Vars::toVar($varName, $getLastPage); 760 | $this->merge($getLastPage); 761 | 762 | return new Page($varName, $this->jsString); 763 | } 764 | 765 | } 766 | -------------------------------------------------------------------------------- /src/Js/Api/Stuctures/BoundingBox.php: -------------------------------------------------------------------------------- 1 | boundingBoxVarName = $boundingBoxVarName; 16 | } 17 | 18 | public function getBoundingBoxVarName(): string 19 | { 20 | return $this->boundingBoxVarName; 21 | } 22 | 23 | public function x(): string { 24 | return $this->boundingBoxVarName.'.x'; 25 | } 26 | 27 | public function y(): string { 28 | return $this->boundingBoxVarName.'.x'; 29 | } 30 | 31 | public function height(): string { 32 | return $this->boundingBoxVarName.'.height'; 33 | } 34 | 35 | public function width(): string { 36 | return $this->boundingBoxVarName.'.width'; 37 | } 38 | 39 | public function centerHeight(): string 40 | { 41 | return "($this->boundingBoxVarName.y + ($this->boundingBoxVarName.height / 2))"; 42 | } 43 | public function centerWidth(): string 44 | { 45 | return "($this->boundingBoxVarName.x + ($this->boundingBoxVarName.width / 2))"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Js/Api/Stuctures/Header.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->value = $value; 22 | } 23 | 24 | public function toArray(): array 25 | { 26 | return [$this->name => $this->value]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Js/Api/Stuctures/NativeValue.php: -------------------------------------------------------------------------------- 1 | data = $data; 14 | } 15 | 16 | public function getData() 17 | { 18 | return $this->data; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Js/Api/Stuctures/SelectOption.php: -------------------------------------------------------------------------------- 1 | value = $value; 25 | $this->label = $label; 26 | $this->number = $number; 27 | } 28 | 29 | public function toArray(): array 30 | { 31 | $arr = []; 32 | 33 | if ($this->value !== null) { 34 | $arr['value'] = $this->value; 35 | } 36 | if ($this->label !== null) { 37 | $arr['label'] = $this->label; 38 | } 39 | if ($this->number !== null) { 40 | $arr['number'] = $this->number; 41 | } 42 | 43 | return $arr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Js/Builder.php: -------------------------------------------------------------------------------- 1 | jsString = rtrim($this->jsString); 19 | $this->prepend('(', false, false); 20 | $this->append(')', false, false); 21 | 22 | return $this; 23 | } 24 | 25 | public function wrapInFunction(string $funcName): Builder 26 | { 27 | $this->wrapInBrackets(); 28 | 29 | $this->prepend($funcName, false, false); 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param Script $builder 36 | */ 37 | public function merge(Script $builder): void 38 | { 39 | $this->append($builder->getJs(), true, false); 40 | } 41 | 42 | /** 43 | * @param int $index 44 | * @return Builder 45 | */ 46 | public function index(int $index): Builder 47 | { 48 | return $this->key((string)$index); 49 | } 50 | 51 | /** 52 | * @param string $key 53 | * @return Builder 54 | */ 55 | public function key(string $key): Builder 56 | { 57 | //removing spacing and commas 58 | $this->jsString = rtrim($this->jsString, " \t\n\r\0\x0B;"); 59 | $this->wrapInBrackets(); 60 | $this->append('[' . $key . ']'); 61 | 62 | return $this; 63 | } 64 | 65 | public function resolve(string $returnVarNameOrExpression = '{}') 66 | { 67 | $this->append('resolve('.$returnVarNameOrExpression.');'); 68 | } 69 | 70 | public function reject(string $returnVarNameOrExpression = '{}') 71 | { 72 | $this->append('reject('.$returnVarNameOrExpression.');'); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/Js/Constructions.php: -------------------------------------------------------------------------------- 1 | &$el) { 34 | if (is_array($el)) { 35 | self::filterNull($el); 36 | } else if (is_null($el)) { 37 | unset($obj[$key]); 38 | } 39 | } 40 | } 41 | 42 | 43 | public static function tryCatch(Script $try, ?Script $catch, string $errorVarName = 'e'): Script 44 | { 45 | if ($catch === null) { 46 | $catch = new Script(); 47 | } 48 | /** @var $catch Script */ 49 | 50 | $tryCatch = new Script(); 51 | 52 | $tryCatch->append('try {', true, false); 53 | $tryCatch->append($try->getJs()); 54 | $tryCatch->append("} catch ($errorVarName) {", true, false); 55 | $tryCatch->append($catch->getJs()); 56 | $tryCatch->append('}', true, false); 57 | 58 | return $tryCatch; 59 | } 60 | 61 | public static function ifElse(Script $if, ?Script $else): Script 62 | { 63 | 64 | } 65 | 66 | //todo make cycle (for / foreach) 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Js/Functions.php: -------------------------------------------------------------------------------- 1 | getData(); 29 | } else { 30 | $buildedArgs[] = Constructions::build($arg, $skipNull); 31 | } 32 | } 33 | 34 | return implode(',', $buildedArgs); 35 | } 36 | 37 | /** 38 | * @param string $funcName 39 | * @param mixed ...$args any arguments 40 | * @return Builder 41 | */ 42 | public static function call(string $funcName, ...$args): Builder 43 | { 44 | $builder = new Builder(); 45 | 46 | $buildedArgs = self::buildArgs($args, true); 47 | 48 | $builder->append($funcName . '(' . $buildedArgs . ')', false, false); 49 | 50 | return $builder; 51 | } 52 | 53 | public static function callAwait(string $funcName, ...$args): Builder 54 | { 55 | $builder = self::call($funcName, ...$args); 56 | $builder->prepend(' ', false, false); 57 | $builder->prepend('await', false, false); 58 | return $builder; 59 | } 60 | 61 | // /** 62 | // * RECOMMENDED 63 | // * this call, in case error, can be catched by simple try->catch 64 | // * 65 | // * @param string $funcName 66 | // * @param mixed ...$args 67 | // * @return Builder 68 | // */ 69 | // public static function callAwaitSafe(string $funcName, ...$args): Builder 70 | // { 71 | // $builder = self::call($funcName, ...$args); 72 | // $builder->wrapInFunction('modules.pss'); 73 | // $builder->prepend(' ', false, false); 74 | // $builder->prepend('await', false, false); 75 | // return $builder; 76 | // } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/Js/Script.php: -------------------------------------------------------------------------------- 1 | jsString = &$customJsStore; 24 | $this->requestTimeout = &$requestTimeout; 25 | } 26 | 27 | /** 28 | * Adding js to the end 29 | * 30 | * @param string $js 31 | * @param bool $needNewLine 32 | * @param bool $semicolon 33 | * @return void 34 | */ 35 | final public function append(string $js, bool $needNewLine = true, bool $semicolon = true): void 36 | { 37 | $this->jsString .= $js; 38 | if ($semicolon) { 39 | $this->jsString .= ';'; 40 | } 41 | if ($needNewLine) { 42 | $this->jsString .= PHP_EOL; 43 | } 44 | } 45 | 46 | /** 47 | * Adding js to the beginning 48 | * 49 | * @param string $js 50 | * @param bool $needNewLine 51 | * @param bool $semicolon 52 | * @return void 53 | */ 54 | final public function prepend(string $js, bool $needNewLine = true, bool $semicolon = true): void 55 | { 56 | if ($semicolon) { 57 | $js .= ';'; 58 | } 59 | if ($needNewLine) { 60 | $js .= PHP_EOL; 61 | } 62 | $this->jsString = $js . $this->jsString; 63 | } 64 | 65 | /** 66 | * get simple Js as a string 67 | * 68 | * @return string 69 | */ 70 | public function getJs(): string 71 | { 72 | return $this->jsString; 73 | } 74 | 75 | public function addToTimeout(int $milliseconds): void 76 | { 77 | $this->requestTimeout += $milliseconds / 1000; 78 | } 79 | 80 | public function setTimeout(int $seconds): void 81 | { 82 | $this->requestTimeout = $seconds; 83 | } 84 | 85 | public function getTimeout(): int 86 | { 87 | return $this->requestTimeout; 88 | } 89 | 90 | public function loadFromFile(string $pathToJs): void 91 | { 92 | $content = file_get_contents($pathToJs); 93 | if ($content === false) { 94 | throw new \RuntimeException('cant read script file'); 95 | } 96 | $this->jsString = $content; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Js/Vars.php: -------------------------------------------------------------------------------- 1 | prepend($varName . ' = (', false, false); 86 | $data->append(')', false, true); 87 | 88 | $rootVarName = explode('.', $varName, 2)[0]; 89 | if (!in_array($rootVarName, self::$varNames, true)) { 90 | $data->prepend($varType . ' ', false, false); 91 | } 92 | } 93 | 94 | /** 95 | * @param Script $data 96 | * @param string $varType let|var|const 97 | * @return string temp var name 98 | */ 99 | public static function toTempVar(Script $data, string $varType = 'let'): string 100 | { 101 | $varName = self::generateRandomVarName(); 102 | self::toVar($varName, $data, $varType); 103 | return $varName; 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/TaskResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 11 | } 12 | 13 | public function isSuccess(): bool 14 | { 15 | return $this->response->status === 'DONE'; 16 | } 17 | 18 | public function getData(): ?object 19 | { 20 | return $this->response->data ?? null; 21 | } 22 | 23 | public function getCreatedAt(): ?int 24 | { 25 | if (!isset($this->response->metadata->created_at)) { 26 | return null; 27 | } 28 | 29 | return (int)($this->response->metadata->created_at / 1000); 30 | } 31 | 32 | public function getRunedAt(): ?int 33 | { 34 | if (!isset($this->response->metadata->runed_at)) { 35 | return null; 36 | } 37 | 38 | return (int)($this->response->metadata->runed_at / 1000); 39 | } 40 | 41 | public function getDoneAt(): ?int 42 | { 43 | if (!isset($this->response->metadata->done_at)) { 44 | return null; 45 | } 46 | return (int)($this->response->metadata->done_at / 1000); 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/TaskServer.php: -------------------------------------------------------------------------------- 1 | connectionConfig = $connectionConfig; 23 | 24 | if ($contextConfig === null) { 25 | $contextConfig = new ContextConfig(); 26 | } 27 | 28 | $this->contextConfig = $contextConfig; 29 | } 30 | 31 | /** 32 | * @return Curl 33 | */ 34 | private function getCurl(): Curl 35 | { 36 | $curl = new Curl(); 37 | if ($this->connectionConfig->inAuthNeeded()) { 38 | $curl->setHeader('Authorization', $this->connectionConfig->getAuthKey()); 39 | } 40 | 41 | return $curl; 42 | } 43 | 44 | /** 45 | * @param Script $script 46 | * @return TaskResponse 47 | * @throws ConnectionError 48 | */ 49 | public function runTask(Script $script): TaskResponse 50 | { 51 | $curl = $this->getCurl(); 52 | $curl->setTimeout($script->getTimeout()); 53 | $curl->setHeader('Content-Type', 'application/json'); 54 | $curl->post('http://' . $this->connectionConfig->getConnectionAddress() . '/task', [ 55 | 'options' => $this->contextConfig->toArray(), 56 | 'script' => $script->getJs() 57 | ]); 58 | 59 | if ($curl->error) { 60 | throw new ConnectionError('cant connect to server'); 61 | } 62 | 63 | return new TaskResponse($curl->response); 64 | } 65 | 66 | //todo implement stats object 67 | /** 68 | * Array of some stats 69 | * @return array 70 | * @throws ConnectionError 71 | */ 72 | public function getStats(): array 73 | { 74 | $curl = $this->getCurl(); 75 | $curl->get('http://' . $this->connectionConfig->getConnectionAddress() . '/stats'); 76 | 77 | if ($curl->error) { 78 | throw new ConnectionError('cant connect to server'); 79 | } 80 | 81 | return $curl->response; 82 | } 83 | 84 | 85 | } 86 | --------------------------------------------------------------------------------