From e2aa324ebc4fc06891ea7444883c39dc2db7fe70 Mon Sep 17 00:00:00 2001 From: Stephen Hartley Date: Tue, 7 Dec 2021 18:08:27 +0000 Subject: [PATCH 01/44] Removed bad example from README (#297) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 84903eaf..f0265db1 100644 --- a/README.md +++ b/README.md @@ -155,10 +155,6 @@ Here are the options available for the browser factory: ```php $page = $browser->createPage(); - -// destination can be specified -$uri = 'http://example.com'; -$page = $browser->createPage($uri); ``` #### Close the browser From 241da11611a6950f6c0cf126a13d083c24b02b07 Mon Sep 17 00:00:00 2001 From: Maxim Grynykha Date: Tue, 7 Dec 2021 20:09:39 +0200 Subject: [PATCH 02/44] Added support for setting HTTP headers (#293) Co-authored-by: enricodias --- README.md | 36 +++++++++-- src/Browser.php | 4 ++ src/Browser/BrowserProcess.php | 5 ++ src/BrowserFactory.php | 87 +++++++++++++++++++++----- src/Communication/Connection.php | 23 +++++++ src/Page.php | 24 +++++-- tests/BrowserFactoryTest.php | 47 ++++++++++++++ tests/Communication/ConnectionTest.php | 13 ++++ 8 files changed, 214 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 84903eaf..f0aaec78 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ - 'headless' => false, // disable headless mode + 'headless' => false, // disable headless mode ]); ``` @@ -117,17 +117,44 @@ or [apix/log](https://github.com/apix/log)). ### Browser Factory +Options set directly in the `createBrowser` method will be used only for a single browser creation. The default options will be ignored. + ```php use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ - 'windowSize' => [1920, 1000], - 'enableImages' => false, + 'windowSize' => [1920, 1000], + 'enableImages' => false, +]); + +// this browser will be created without any options +$browser2 = $browserFactory->createBrowser(); +``` + +Options set using the `setOptions` and `addOptions` methods will persist. + +```php +$browserFactory->setOptions([ + 'windowSize' => [1920, 1000], ]); + +// both browser will have the same 'windowSize' option +$browser1 = $browserFactory->createBrowser(); +$browser2 = $browserFactory->createBrowser(); + +$browserFactory->addOptions(['enableImages' => false]); + +// this browser will have both the 'windowSize' and 'enableImages' options +$browser3 = $browserFactory->createBrowser(); + +$browserFactory->addOptions(['enableImages' => true]); + +// this browser will have the previous 'windowSize', but 'enableImages' will be true +$browser4 = $browserFactory->createBrowser(); ``` -#### Options +#### Available options Here are the options available for the browser factory: @@ -148,6 +175,7 @@ Here are the options available for the browser factory: | `userAgent` | none | User agent to use for the whole browser (see page api for alternative) | | `userDataDir` | none | Chrome user data dir (default: a new empty dir is generated temporarily) | | `windowSize` | none | Size of the window. usage: `$width, $height` - see also Page::setViewport | +| `headers` | none | Set an array of custom HTTP headers | ### Browser API diff --git a/src/Browser.php b/src/Browser.php index 23bb1ed8..5d635f55 100644 --- a/src/Browser.php +++ b/src/Browser.php @@ -75,6 +75,10 @@ public function __construct(Connection $connection) // enable target discovery $connection->sendMessageSync(new Message('Target.setDiscoverTargets', ['discover' => true])); + + // set up http headers + $headers = $connection->getConnectionHttpHeaders(); + $connection->sendMessageSync(new Message('Network.setExtraHTTPHeaders', $headers)); } /** diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index d01520b6..dc6ca619 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -150,6 +150,11 @@ public function start($binary, $options): void $connection->setConnectionDelay($options['connectionDelay']); } + // connection headers + if (\array_key_exists('headers', $options)) { + $connection->setConnectionHttpHeaders($options['headers']); + } + // set connection to allow killing chrome $this->connection = $connection; diff --git a/src/BrowserFactory.php b/src/BrowserFactory.php index f0c90ea6..03632e9c 100644 --- a/src/BrowserFactory.php +++ b/src/BrowserFactory.php @@ -23,6 +23,28 @@ class BrowserFactory { protected $chromeBinary; + /** + * Options for browser creation. + * + * - connectionDelay: amount of time in seconds to slows down connection for debugging purposes (default: none) + * - customFlags: array of custom flag to flags to pass to the command line + * - debugLogger: resource string ("php://stdout"), resource or psr-3 logger instance (default: none) + * - enableImages: toggle the loading of images (default: true) + * - envVariables: array of environment variables to pass to the process (example DISPLAY variable) + * - headless: whether chrome should be started headless (default: true) + * - ignoreCertificateErrors: set chrome to ignore ssl errors + * - keepAlive: true to keep alive the chrome instance when the script terminates (default: false) + * - noSandbox: enable no sandbox mode (default: false) + * - proxyServer: a proxy server, ex: 127.0.0.1:8080 (default: none) + * - sendSyncDefaultTimeout: maximum time in ms to wait for synchronous messages to send (default 5000 ms) + * - startupTimeout: maximum time in seconds to wait for chrome to start (default: 30 sec) + * - userAgent: user agent to use for the browser + * - userDataDir: chrome user data dir (default: a new empty dir is generated temporarily) + * - windowSize: size of the window, ex: [1920, 1080] (default: none) + * - headers: set an array of custom HTTP headers + */ + protected $options = []; + public function __construct(string $chromeBinary = null) { $this->chromeBinary = $chromeBinary ?? (new AutoDiscover())->guessChromeBinaryPath(); @@ -31,26 +53,13 @@ public function __construct(string $chromeBinary = null) /** * Start a chrome process and allows to interact with it. * - * @param array $options options for browser creation: - * - connectionDelay: amount of time in seconds to slows down connection for debugging purposes (default: none) - * - customFlags: array of custom flag to flags to pass to the command line - * - debugLogger: resource string ("php://stdout"), resource or psr-3 logger instance (default: none) - * - enableImages: toggle the loading of images (default: true) - * - envVariables: array of environment variables to pass to the process (example DISPLAY variable) - * - headless: whether chrome should be started headless (default: true) - * - ignoreCertificateErrors: set chrome to ignore ssl errors - * - keepAlive: true to keep alive the chrome instance when the script terminates (default: false) - * - noSandbox: enable no sandbox mode (default: false) - * - proxyServer: a proxy server, ex: 127.0.0.1:8080 (default: none) - * - sendSyncDefaultTimeout: maximum time in ms to wait for synchronous messages to send (default 5000 ms) - * - startupTimeout: maximum time in seconds to wait for chrome to start (default: 30 sec) - * - userAgent: user agent to use for the browser - * - userDataDir: chrome user data dir (default: a new empty dir is generated temporarily) - * - windowSize: size of the window, ex: [1920, 1080] (default: none) + * @see BrowserFactory::$options + * + * @param array $options overwrite options for browser creation * * @return ProcessAwareBrowser a Browser instance to interact with the new chrome process */ - public function createBrowser(array $options = []): ProcessAwareBrowser + public function createBrowser(?array $options = null): ProcessAwareBrowser { // create logger from options $logger = self::createLogger($options); @@ -58,6 +67,10 @@ public function createBrowser(array $options = []): ProcessAwareBrowser // create browser process $browserProcess = new BrowserProcess($logger); + if (null === $options) { + $options = $this->options; + } + // instruct the runtime to kill chrome and clean temp files on exit if (!\array_key_exists('keepAlive', $options) || !$options['keepAlive']) { \register_shutdown_function([$browserProcess, 'kill']); @@ -69,6 +82,21 @@ public function createBrowser(array $options = []): ProcessAwareBrowser return $browserProcess->getBrowser(); } + public function addHeader(string $name, string $value): void + { + $this->options['headers'][$name] = $value; + } + + /** + * @param array $headers + */ + public function addHeaders(array $headers): void + { + foreach ($headers as $name => $value) { + $this->addHeader($name, $value); + } + } + /** * Connects to an existing browser using it's web socket uri. * @@ -124,6 +152,31 @@ public static function connectToBrowser(string $uri, array $options = []): Brows return new Browser($connection); } + /** + * Set default options to be used in all browser instances. + * + * @see BrowserFactory::$options + */ + public function setOptions(array $options): void + { + $this->options = $options; + } + + /** + * Add or overwrite options to the default options list. + * + * @see BrowserFactory::$options + */ + public function addOptions(array $options): void + { + $this->options = \array_merge($this->options, $options); + } + + public function getOptions(): array + { + return $this->options; + } + /** * Create a logger instance from given options. * diff --git a/src/Communication/Connection.php b/src/Communication/Connection.php index d5faff7b..917e367c 100644 --- a/src/Communication/Connection.php +++ b/src/Communication/Connection.php @@ -85,6 +85,11 @@ class Connection extends EventEmitter implements LoggerAwareInterface */ protected $receivedData = []; + /** + * @var array + */ + protected $httpHeaders = []; + /** * CommunicationChannel constructor. * @@ -127,6 +132,24 @@ public function setConnectionDelay(int $delay): void $this->delay = $delay; } + /** + * @param array $headers + * + * @return void + */ + public function setConnectionHttpHeaders(array $headers): void + { + $this->httpHeaders = $headers; + } + + /** + * @return array + */ + public function getConnectionHttpHeaders(): array + { + return $this->httpHeaders; + } + /** * Gets the default timeout used when sending a message synchronously. * diff --git a/src/Page.php b/src/Page.php index 2b590570..8c6d2382 100644 --- a/src/Page.php +++ b/src/Page.php @@ -148,10 +148,9 @@ public function getSession(): Session public function setBasicAuthHeader(string $username, string $password): void { $header = \base64_encode($username.':'.$password); - $this->getSession()->sendMessage(new Message( - 'Network.setExtraHTTPHeaders', - ['headers' => ['Authorization' => 'Basic '.$header]] - )); + $this->setExtraHTTPHeaders([ + 'Authorization' => 'Basic '.$header, + ]); } /** @@ -167,6 +166,23 @@ public function setDownloadPath(string $path): void )); } + /** + * Set extra http headers. + * + * If headers are not passed, all instances of Page::class will use global settings from the BrowserFactory::class + * + * @see https://chromedevtools.github.io/devtools-protocol/1-2/Network/#method-setExtraHTTPHeaders + * + * @param array $headers + */ + public function setExtraHTTPHeaders(array $headers = []): void + { + $this->getSession()->sendMessage(new Message( + 'Network.setExtraHTTPHeaders', + $headers + )); + } + /** * @param string $url * @param array $options diff --git a/tests/BrowserFactoryTest.php b/tests/BrowserFactoryTest.php index 4843334d..c125ed93 100644 --- a/tests/BrowserFactoryTest.php +++ b/tests/BrowserFactoryTest.php @@ -59,6 +59,53 @@ public function testUserAgentOption(): void $this->assertEquals('foo bar baz', $response); } + public function testAddHeaders(): void + { + $factory = new BrowserFactory(); + + $factory->addHeader('header_name', 'header_value'); + $factory->addHeaders(['header_name2' => 'header_value2']); + + $expected = [ + 'header_name' => 'header_value', + 'header_name2' => 'header_value2', + ]; + + $this->assertSame($expected, $factory->getOptions()['headers']); + } + + public function testOptions(): void + { + $factory = new BrowserFactory(); + + $headers = ['header_name' => 'header_value']; + $options = ['userAgent' => 'foo bar baz']; + $modifiedOptions = ['userAgent' => 'foo bar']; + + $factory->addHeaders($headers); + $factory->addOptions($options); + + $expected = \array_merge(['headers' => $headers], $options); + + $this->assertSame($expected, $factory->getOptions()); + + // test overwriting + $factory->addOptions($modifiedOptions); + + $expected['userAgent'] = 'foo bar'; + + $this->assertSame($expected, $factory->getOptions()); + + // test removing options + $factory->setOptions($modifiedOptions); + + $this->assertSame($modifiedOptions, $factory->getOptions()); + + $factory->setOptions([]); + + $this->assertSame([], $factory->getOptions()); + } + public function testConnectToBrowser(): void { // create a browser diff --git a/tests/Communication/ConnectionTest.php b/tests/Communication/ConnectionTest.php index eeb26b39..196f35cb 100644 --- a/tests/Communication/ConnectionTest.php +++ b/tests/Communication/ConnectionTest.php @@ -132,6 +132,19 @@ public function testSendMessageWorksWithDelay(): void ); } + public function testConnectionHttpHeaders(): void + { + $connection = new Connection($this->mocSocket); + + $header = [ + 'header_name' => 'header_value', + ]; + + $connection->setConnectionHttpHeaders($header); + + $this->assertSame($header, $connection->getConnectionHttpHeaders()); + } + public function testSendMessageSync(): void { $connection = new Connection($this->mocSocket); From b297117c9fc6c8e9a672cbd9c8ab1aab3aaa7e0d Mon Sep 17 00:00:00 2001 From: Maxim Grynykha Date: Tue, 7 Dec 2021 20:16:30 +0200 Subject: [PATCH 03/44] Update documentation for readme.md & phpdoc (#291) Co-authored-by: Graham Campbell --- README.md | 148 ++++++++++++++++++++++------------------- src/BrowserFactory.php | 30 ++++----- src/Page.php | 56 ++++++++++++++-- 3 files changed, 148 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index f0265db1..e7e3bb49 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Can be used synchronously and asynchronously! - Open chrome or chromium browser from php - Create pages and navigate to pages - Take screenshots -- Evaluate javascript in the page +- Evaluate javascript on the page - Make PDF - Emulate mouse - Emulate keyboard @@ -26,7 +26,7 @@ Happy browsing! Requires PHP 7.3-8.0 and a chrome/chromium 65+ executable. -Note that the library is only tested on Linux but is compatible with MacOS and Windows. +Note that the library is only tested on Linux but is compatible with macOS and Windows. ## Installation @@ -41,8 +41,7 @@ $ composer require chrome-php/chrome ## Usage -It uses a simple and understandable API to start chrome, to open pages, to take screenshots, -to crawl websites... and almost everything that you can do with chrome as a human. +It uses a simple and understandable API to start chrome, to open pages, take screenshots, crawl websites... and almost everything that you can do with chrome as a human. ```php use HeadlessChromium\BrowserFactory; @@ -53,7 +52,7 @@ $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser(); try { - // creates a new page and navigate to an url + // creates a new page and navigate to an URL $page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); @@ -76,7 +75,7 @@ try { When starting, the factory will look for the environment variable ``"CHROME_PATH"`` to use as the chrome executable. If the variable is not found, it will try to guess the correct executable path according to your OS or use ``"chrome"`` as the default. -You also explicitly set any executable of your choice when creating a new object. For instance ``"chromium-browser"``: +You are also able to explicitly set up any executable of your choice when creating a new object. For instance ``"chromium-browser"``: ```php use HeadlessChromium\BrowserFactory; @@ -95,7 +94,7 @@ use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ - 'headless' => false, // disable headless mode + 'headless' => false, // disable headless mode ]); ``` @@ -108,7 +107,7 @@ Other debug options: ] ``` -About ``debugLogger``: this can be any of a resource string, a resource or an object implementing +About ``debugLogger``: this can be any of a resource string, a resource, or an object implementing ``LoggerInterface`` from Psr\Log (such as [monolog](https://github.com/Seldaek/monolog) or [apix/log](https://github.com/apix/log)). @@ -134,18 +133,18 @@ Here are the options available for the browser factory: | Option name | Default | Description | |---------------------------|---------|----------------------------------------------------------------------------------------------| | `connectionDelay` | `0` | Delay to apply between each operation for debugging purposes | -| `customFlags` | none | Array of flags to pass to the command line. Eg: `['--option1', '--option2=someValue']` | +| `customFlags` | none | An array of flags to pass to the command line. Eg: `['--option1', '--option2=someValue']` | | `debugLogger` | `null` | A string (e.g "php://stdout"), or resource, or PSR-3 logger instance to print debug messages | | `enableImages` | `true` | Toggles loading of images | -| `envVariables` | none | Array of environment variables to pass to the process (example DISPLAY variable) | +| `envVariables` | none | An array of environment variables to pass to the process (example DISPLAY variable) | | `headless` | `true` | Enable or disable headless mode | | `ignoreCertificateErrors` | `false` | Set chrome to ignore ssl errors | | `keepAlive` | `false` | Set to `true` to keep alive the chrome instance when the script terminates | -| `noSandbox` | `false` | Useful to run in a docker container | +| `noSandbox` | `false` | Enable no sandbox mode, useful to run in a docker container | | `proxyServer` | none | Proxy server to use. usage: `127.0.0.1:8080` (authorisation with credentials does not work) | | `sendSyncDefaultTimeout` | `5000` | Default timeout (ms) for sending sync messages | | `startupTimeout` | `30` | Maximum time in seconds to wait for chrome to start | -| `userAgent` | none | User agent to use for the whole browser (see page api for alternative) | +| `userAgent` | none | User agent to use for the whole browser (see page API for alternative) | | `userDataDir` | none | Chrome user data dir (default: a new empty dir is generated temporarily) | | `windowSize` | none | Size of the window. usage: `$width, $height` - see also Page::setViewport | @@ -160,7 +159,7 @@ $page = $browser->createPage(); #### Close the browser ```php - $browser->close(); +$browser->close(); ``` ### Set a script to evaluate before every page created by this browser will navigate @@ -177,7 +176,7 @@ window.navigator.permissions.query = (parameters) => ( ### Page API -#### Navigate to an url +#### Navigate to an URL ```php // navigate @@ -187,7 +186,7 @@ $navigation = $page->navigate('http://example.com'); $navigation->waitForNavigation(); ``` -When Using ``$navigation->waitForNavigation()`` you will wait for 30sec until the page event "loaded" is triggered. +When using ``$navigation->waitForNavigation()`` you will wait for 30sec until the page event "loaded" is triggered. You can change the timeout or the event to listen for: ```php @@ -201,9 +200,9 @@ Available events (in the order they trigger): - ``Page::LOAD``: (default) page and all resources are loaded - ``Page::NETWORK_IDLE``: page has loaded, and no network activity has occurred for at least 500ms -When you want to wait for the page to navigate there are 2 main issues that may occur. -First the page is too long to load and second the page you were waiting to be loaded has been replaced. -The good news is that you can handle those issues using a good old try catch: +When you want to wait for the page to navigate 2 main issues may occur. +First, the page is too long to load and second, the page you were waiting to be loaded has been replaced. +The good news is that you can handle those issues using a good old try-catch: ```php use HeadlessChromium\Exception\OperationTimedOut; @@ -237,7 +236,7 @@ $value = $evaluation->getReturnValue(); ``` -Sometime the script you evaluate will click a link or submit a form, in this case the page will reload and you +Sometimes the script you evaluate will click a link or submit a form, in this case, the page will reload and you will want to wait for the new page to reload. You can achieve this by using ``$page->evaluate('some js that will reload the page')->waitForPageReload()``. @@ -245,7 +244,7 @@ An example is available in [form-submit.php](./examples/form-submit.php) #### Call a function -This is an alternative to ``evaluate`` that allows to call a given function with the given arguments in the page context: +This is an alternative to ``evaluate`` that allows calling a given function with the given arguments in the page context: ```php $evaluation = $page->callFunction( @@ -268,7 +267,7 @@ $page->addScriptTag([ $page->evaluate('$(".my.element").html()'); ``` -You can also use an url to feed the src attribute: +You can also use an URL to feed the src attribute: ```php $page->addScriptTag([ @@ -306,14 +305,14 @@ $page->addPreScript($script, ['onLoad' => true]); #### Set viewport size -This features allows to change the size of the viewport (emulation) for the current page without affecting the size of +This feature allows changing the size of the viewport (emulation) for the current page without affecting the size of all the browser's pages (see also option ``"windowSize"`` of [BrowserFactory::createBrowser](#options)). ```php $width = 600; $height = 300; $page->setViewport($width, $height) - ->await(); // wait for operation to complete + ->await(); // wait for the operation to complete ``` #### Make a screenshot @@ -335,15 +334,9 @@ $screenshot = $page->screenshot([ $screenshot->saveToFile('/some/place/file.jpg'); ``` -**choose an area** - -You can use the option "clip" in order to choose an area for the screenshot (TODO exemple) - -**take a full page screenshot** +**Screenshot an area on a page** -You can also take a screenshot for the full layout (not only the layout) using ``$page->getFullPageClip`` (TODO exemple) - -TODO ``Page.getFullPageClip();`` +You can use the option "clip" to choose an area on a page for the screenshot ```php use HeadlessChromium\Clip; @@ -370,6 +363,27 @@ $screenshot = $page->screenshot([ $screenshot->saveToFile('/some/place/file.jpg'); ``` +**Full-page screenshot** + +You can also take a screenshot for the full-page layout (not only the viewport) using ``$page->getFullPageClip`` with attribute ``captureBeyondViewport = true`` + +```php +// navigate +$navigation = $page->navigate('https://example.com'); + +// wait for the page to be loaded +$navigation->waitForNavigation(); + +$screenshot = $page->screenshot([ + 'captureBeyondViewport' => true, + 'clip' => $page->getFullPageClip(), + 'format' => 'jpeg', // default to 'png' - possible values: 'png', 'jpeg', +]); + +// save the screenshot +$screenshot->saveToFile('/some/place/file.jpg'); +``` + #### Print as PDF ```php @@ -383,16 +397,16 @@ $options = [ 'landscape' => true, // default to false 'printBackground' => true, // default to false 'displayHeaderFooter' => true, // default to false - 'preferCSSPageSize' => true, // default to false ( reads parameters directly from @page ) - 'marginTop' => 0.0, // defaults to ~0.4 (must be float, value in inches) - 'marginBottom' => 1.4, // defaults to ~0.4 (must be float, value in inches) - 'marginLeft' => 5.0, // defaults to ~0.4 (must be float, value in inches) - 'marginRight' => 1.0, // defaults to ~0.4 (must be float, value in inches) - 'paperWidth' => 6.0, // defaults to 8.5 (must be float, value in inches) - 'paperHeight' => 6.0, // defaults to 8.5 (must be float, value in inches) + 'preferCSSPageSize' => true, // default to false (reads parameters directly from @page) + 'marginTop' => 0.0, // defaults to ~0.4 (must be a float, value in inches) + 'marginBottom' => 1.4, // defaults to ~0.4 (must be a float, value in inches) + 'marginLeft' => 5.0, // defaults to ~0.4 (must be a float, value in inches) + 'marginRight' => 1.0, // defaults to ~0.4 (must be a float, value in inches) + 'paperWidth' => 6.0, // defaults to 8.5 (must be a float, value in inches) + 'paperHeight' => 6.0, // defaults to 8.5 (must be a float, value in inches) 'headerTemplate' => '
foo
', // see details above 'footerTemplate' => '
foo
', // see details above - 'scale' => 1.2, // defaults to 1.0 (must be float) + 'scale' => 1.2, // defaults to 1.0 (must be a float) ]; // print as pdf (in memory binaries) @@ -415,7 +429,7 @@ echo base64_decode($pdf->getBase64()); Options `headerTempalte` and `footerTempalte`: -Should be valid HTML markup with following classes used to inject printing values into them: +Should be valid HTML markup with the following classes used to inject printing values into them: - date: formatted print date - title: document title - url: document location @@ -437,17 +451,17 @@ The mouse API is dependent on the page instance and allows you to control the mo ```php $page->mouse() - ->move(10, 20) // Moves mouse to position x=10;y=20 - ->click() // left click on position set above - ->move(100, 200, ['steps' => 5]) // move mouse to x=100;y=200 in 5 equal steps - ->click(['button' => Mouse::BUTTON_RIGHT]; // right click on position set above + ->move(10, 20) // Moves mouse to position x=10; y=20 + ->click() // left-click on position set above + ->move(100, 200, ['steps' => 5]) // move mouse to x=100; y=200 in 5 equal steps + ->click(['button' => Mouse::BUTTON_RIGHT]; // right-click on position set above // given the last click was on a link, the next step will wait // for the page to load after the link was clicked $page->waitForReload(); ``` -You can emulate the mouse wheel to scroll up and down in a page, frame or element. +You can emulate the mouse wheel to scroll up and down in a page, frame, or element. ```php $page->mouse() @@ -461,7 +475,7 @@ The `find` method will search for elements using [querySelector](https://develop ```php try { - $page->mouse()->find('#a')->click(); // find and click on element with id "a" + $page->mouse()->find('#a')->click(); // find and click at an element with id "a" $page->mouse()->find('.a', 10); // find the 10th or last element with class "a" } catch (ElementNotFoundException $exception) { @@ -469,7 +483,7 @@ try { } ``` -This method will attempt scroll right and down to bring the element to the visible screen. If the element is inside an internal scrollable section, try moving the mouse to inside that section first. +This method will attempt to scroll right and down to bring the element to the visible screen. If the element is inside an internal scrollable section, try moving the mouse to inside that section first. ### Keyboard API @@ -484,29 +498,29 @@ $page->keyboard() To impersonate a real user you may want to add a delay between each keystroke using the ```setKeyInterval``` method: ```php -$page->keyboard()->setKeyInterval(10); // sets a delay of 10 miliseconds between keystrokes +$page->keyboard()->setKeyInterval(10); // sets a delay of 10 milliseconds between keystrokes ``` #### Key combinations -The methods `press`, `type` and `release` can be used to send key combinations such as `ctrl + v`. +The methods `press`, `type`, and `release` can be used to send key combinations such as `ctrl + v`. ```php // ctrl + a to select all text $page->keyboard() - ->press(' control ') // key names are case insensitive and trimmed - ->type('a') // press and release + ->press('control') // key names are case insensitive and trimmed + ->type('a') // press and release ->release('Control'); // ctrl + c to copy and ctrl + v to paste it twice $page->keyboard() ->press('Ctrl') // alias for Control ->type('c') - ->type('V') // upper and lower case should behave the same way - ->release(); // release all + ->type('V') // upper and lower cases should behave the same way + ->release(); // release all ``` -You can press the same key several times in sequence, this is equivalent of a user pressing and holding the key. The release event, however, will be sent only once per key. +You can press the same key several times in sequence, this is the equivalent to a user pressing and holding the key. The release event, however, will be sent only once per key. #### Key aliases @@ -575,13 +589,13 @@ if ($cookieBar) { ### Set user agent -You can set an user agent per page: +You can set up a user-agent per page: ```php -$page->setUserAgent('my user agent'); +$page->setUserAgent('my user-agent'); ``` -See also BrowserFactory option ``userAgent`` to set it for the whole browser. +See also BrowserFactory option ``userAgent`` to set up it for the whole browser. Advanced usage @@ -596,7 +610,7 @@ Example: use HeadlessChromium\Communication\Connection; use HeadlessChromium\Communication\Message; -// chrome devtools uri +// chrome devtools URI $webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx'; // create a connection @@ -610,7 +624,7 @@ $responseReader = $connection->sendMessage(new Message('Target.activateTarget', $response = $responseReader->waitForResponse(1000); ``` -### Create a session and send message to the target +### Create a session and send a message to the target ```php // given a target id @@ -628,7 +642,7 @@ $response = $session->sendMessageSync(new Message('Page.reload')); You can ease the debugging by setting a delay before each operation is made: ```php - $connection->setConnectionDelay(500); // wait for 500 ms between each operation to ease debugging + $connection->setConnectionDelay(500); // wait for 500ms between each operation to ease debugging ``` ### Browser (standalone) @@ -637,10 +651,10 @@ You can ease the debugging by setting a delay before each operation is made: use HeadlessChromium\Communication\Connection; use HeadlessChromium\Browser; -// chrome devtools uri +// chrome devtools URI $webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx'; -// create connection given a web socket uri +// create connection given a WebSocket URI $connection = new Connection($webSocketUri); $connection->connect(); @@ -650,7 +664,7 @@ $browser = new Browser($connection); ### Interacting with DOM -Find one element on page by css selector: +Find one element on a page by CSS selector: ```php $page = $browser->createPage(); @@ -659,14 +673,14 @@ $page->navigate('http://example.com')->waitForNavigation(); $elem = $page->dom()->querySelector('#index_email'); ``` -Find all elements in another element by css selector: +Find all elements inside another element by CSS selector: ```php $elem = $page->dom()->querySelector('#index_email'); $elem->querySelectorAll('a.link'); ``` -Find all elements on page by xpath selector: +Find all elements on a page by XPath selector: ```php $page = $browser->createPage(); @@ -675,14 +689,14 @@ $page->navigate('http://example.com')->waitForNavigation(); $elem = $page->dom()->search('//div/*/a'); ``` -You can send text to element or click on it: +You can send out a text to an element or click on it: ```php $elem->click(); $elem->sendKeys('Sample text'); ``` -You can upload file to file from input: +You can upload file to file from the input: ```php $elem->uploadFile('/path/to/file'); diff --git a/src/BrowserFactory.php b/src/BrowserFactory.php index f0c90ea6..9a3e6c36 100644 --- a/src/BrowserFactory.php +++ b/src/BrowserFactory.php @@ -32,21 +32,21 @@ public function __construct(string $chromeBinary = null) * Start a chrome process and allows to interact with it. * * @param array $options options for browser creation: - * - connectionDelay: amount of time in seconds to slows down connection for debugging purposes (default: none) - * - customFlags: array of custom flag to flags to pass to the command line - * - debugLogger: resource string ("php://stdout"), resource or psr-3 logger instance (default: none) - * - enableImages: toggle the loading of images (default: true) - * - envVariables: array of environment variables to pass to the process (example DISPLAY variable) - * - headless: whether chrome should be started headless (default: true) - * - ignoreCertificateErrors: set chrome to ignore ssl errors - * - keepAlive: true to keep alive the chrome instance when the script terminates (default: false) - * - noSandbox: enable no sandbox mode (default: false) - * - proxyServer: a proxy server, ex: 127.0.0.1:8080 (default: none) - * - sendSyncDefaultTimeout: maximum time in ms to wait for synchronous messages to send (default 5000 ms) - * - startupTimeout: maximum time in seconds to wait for chrome to start (default: 30 sec) - * - userAgent: user agent to use for the browser - * - userDataDir: chrome user data dir (default: a new empty dir is generated temporarily) - * - windowSize: size of the window, ex: [1920, 1080] (default: none) + * - connectionDelay: Delay to apply between each operation for debugging purposes (default: none) + * - customFlags: An array of flags to pass to the command line. + * - debugLogger: A string (e.g "php://stdout"), or resource, or PSR-3 logger instance to print debug messages (default: none) + * - enableImages: Toggles loading of images (default: true) + * - envVariables: An array of environment variables to pass to the process (example DISPLAY variable) + * - headless: Enable or disable headless mode (default: true) + * - ignoreCertificateErrors: Set chrome to ignore ssl errors + * - keepAlive: Set to `true` to keep alive the chrome instance when the script terminates (default: false) + * - noSandbox: Enable no sandbox mode, useful to run in a docker container (default: false) + * - proxyServer: Proxy server to use. ex: `127.0.0.1:8080` (default: none) + * - sendSyncDefaultTimeout: Default timeout (ms) for sending sync messages (default 5000 ms) + * - startupTimeout: Maximum time in seconds to wait for chrome to start (default: 30 sec) + * - userAgent: User agent to use for the whole browser + * - userDataDir: Chrome user data dir (default: a new empty dir is generated temporarily) + * - windowSize: Size of the window. ex: `[1920, 1080]` (default: none) * * @return ProcessAwareBrowser a Browser instance to interact with the new chrome process */ diff --git a/src/Page.php b/src/Page.php index 2b590570..1fd3e02c 100644 --- a/src/Page.php +++ b/src/Page.php @@ -425,11 +425,11 @@ private function waitForReloadGenerator($eventName, $loaderId) } /** - * Get a clip that uses the full layout page, not only the viewport. + * Get a clip that uses the full screen layout (only the viewport). * - * This method is synchronous + * This method is synchronous. * - * Fullpage screenshot exemple: + * Full-screen screenshot example: * * ```php * $page @@ -453,11 +453,59 @@ public function getFullPageClip(int $timeout = null): Clip /** * Take a screenshot. * - * Usage: + * Simple screenshot: * * ```php * $page->screenshot()->saveToFile('/tmp/image.jpg'); * ``` + * -------------------------------------------------------------------------------- + * + * Screenshot an area on a page: + * + * ```php + * use HeadlessChromium\Clip; + * + * // navigate + * $navigation = $page->navigate('http://example.com'); + * + * // wait for the page to be loaded + * $navigation->waitForNavigation(); + * + * // create a rectangle by specifying to left corner coordinates + width and height + * $x = 10; + * $y = 10; + * $width = 100; + * $height = 100; + * $clip = new Clip($x, $y, $width, $height); + * + * // take the screenshot (in memory binaries) + * $screenshot = $page->screenshot([ + * 'clip' => $clip, + * ]); + * + * // save the screenshot + * $screenshot->saveToFile('/some/place/file.jpg'); + * ``` + * -------------------------------------------------------------------------------- + * + * Full-page screenshot (not only the viewport): + * + * ```php + * // navigate + * $navigation = $page->navigate('https://example.com'); + * + * // wait for the page to be loaded + * $navigation->waitForNavigation(); + * + * $screenshot = $page->screenshot([ + * 'captureBeyondViewport' => true, + * 'clip' => $page->getFullPageClip(), + * 'format' => 'jpeg', // default to 'png' - possible values: 'png', 'jpeg', + * ]); + * + * // save the screenshot + * $screenshot->saveToFile('/some/place/file.jpg'); + * ``` * * @param array $options * - format: "png"|"jpg" default "png" From 6293a52d9298c3d8a28095235209aa957cf4cc77 Mon Sep 17 00:00:00 2001 From: Rigel Kent Carbonel Date: Wed, 8 Dec 2021 02:16:42 +0800 Subject: [PATCH 04/44] Suppress the depreciation error in PHP 8.1 (#296) --- src/Communication/Response.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Communication/Response.php b/src/Communication/Response.php index 9129e90a..dddc6a1f 100644 --- a/src/Communication/Response.php +++ b/src/Communication/Response.php @@ -101,6 +101,7 @@ public function getData(): array /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return \array_key_exists($offset, $this->data); @@ -109,6 +110,7 @@ public function offsetExists($offset) /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->data[$offset]; @@ -117,6 +119,7 @@ public function offsetGet($offset) /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { throw new \Exception('Responses are immutable'); @@ -125,6 +128,7 @@ public function offsetSet($offset, $value): void /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Exception('Responses are immutable'); From e9f10d0f7af33b4a58132891095c7cc525af3f11 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 18:57:30 +0000 Subject: [PATCH 05/44] Switch to monolog for logging to enable support for `psr/log` 2 and 3 (#301) --- composer.json | 6 +++--- src/BrowserFactory.php | 32 +++++++++++++++++--------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 3044cf40..0a9fd862 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,16 @@ ], "require": { "php": "^7.3 || ^8.0", - "apix/log": "^1.2", "chrome-php/wrench": "^1.0", "evenement/evenement": "^3.0.1", - "psr/log": "^1.1", + "monolog/monolog": "^1.26 || ^2.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", "symfony/filesystem": "^4.4 || ^5.0 || ^6.0", "symfony/process": "^4.4 || ^5.0 || ^6.0" }, "require-dev":{ "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^9.5.9", + "phpunit/phpunit": "^9.5.10", "symfony/var-dumper": "^4.4 || ^5.0 || ^6.0" }, "autoload":{ diff --git a/src/BrowserFactory.php b/src/BrowserFactory.php index 5ec49d45..8155c688 100644 --- a/src/BrowserFactory.php +++ b/src/BrowserFactory.php @@ -11,11 +11,14 @@ namespace HeadlessChromium; -use Apix\Log\Logger\Stream as StreamLogger; use HeadlessChromium\Browser\BrowserProcess; use HeadlessChromium\Browser\ProcessAwareBrowser; use HeadlessChromium\Communication\Connection; use HeadlessChromium\Exception\BrowserConnectionFailed; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Symfony\Component\Process\Process; use Wrench\Exception\HandshakeException; @@ -55,22 +58,20 @@ public function __construct(string $chromeBinary = null) * * @see BrowserFactory::$options * - * @param array $options overwrite options for browser creation + * @param array|null $options overwrite options for browser creation * * @return ProcessAwareBrowser a Browser instance to interact with the new chrome process */ public function createBrowser(?array $options = null): ProcessAwareBrowser { + $options = $options ?? $this->options; + // create logger from options $logger = self::createLogger($options); // create browser process $browserProcess = new BrowserProcess($logger); - if (null === $options) { - $options = $this->options; - } - // instruct the runtime to kill chrome and clean temp files on exit if (!\array_key_exists('keepAlive', $options) || !$options['keepAlive']) { \register_shutdown_function([$browserProcess, 'kill']); @@ -179,21 +180,22 @@ public function getOptions(): array /** * Create a logger instance from given options. - * - * @param array $options - * - * @return StreamLogger|null */ - private static function createLogger($options) + private static function createLogger(array $options): LoggerInterface { - // prepare logger $logger = $options['debugLogger'] ?? null; - // create logger from string name or resource + if ($logger instanceof LoggerInterface) { + return $logger; + } + if (\is_string($logger) || \is_resource($logger)) { - $logger = new StreamLogger($logger); + $log = new Logger('chrome'); + $log->pushHandler(new StreamHandler($logger)); + + return $log; } - return $logger; + return new NullLogger(); } } From 3098f73eeea8ad6b7dc8ec31bc9865d2435589b8 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 19:34:59 +0000 Subject: [PATCH 06/44] Release 1.2.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c00f217..d74bf469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # CHANGELOG +## 1.2.1 (2021-12-07) + +* Partial PHP 8.1 support + + ## 1.2.0 (2021-11-20) * Dropped `--disable-default-apps` and `--disable-extensions` by default From df0f007506c622f792b450d93ca77b877d086969 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 19:38:13 +0000 Subject: [PATCH 07/44] Update composer.json --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3044cf40..bdebfa76 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,13 @@ "authors": [ { "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { "name": "Enrico Dias", - "email": "enricodias@gmail.com" + "email": "enricodias@gmail.com", + "homepage": "https://github.com/enricodias" } ], "require": { From 53bc5a73820941d7526706938acd820e22eee82a Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 19:41:48 +0000 Subject: [PATCH 08/44] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74bf469..bf51588f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # CHANGELOG +# 1.3.0 (UPCOMING) + +* Added support for setting HTTP headers +* Added support for `psr/log` 2 and 3 + + ## 1.2.1 (2021-12-07) * Partial PHP 8.1 support From b02d081ed61a455584e71c5420f91ce745d9aa2d Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 20:41:39 +0000 Subject: [PATCH 09/44] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1e7adddd..e1c8a0c7 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.3 || ^8.0", - "chrome-php/wrench": "^1.0", + "chrome-php/wrench": "^1.1", "evenement/evenement": "^3.0.1", "monolog/monolog": "^1.26 || ^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", From cc3a46a6e7feabab48395cd1e9060c698df49f4a Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 7 Dec 2021 20:44:49 +0000 Subject: [PATCH 10/44] Release 1.3.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf51588f..e02cee03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # CHANGELOG -# 1.3.0 (UPCOMING) +## 1.3.0 (2021-12-07) * Added support for setting HTTP headers * Added support for `psr/log` 2 and 3 From 9a1fa1c0c03cd8c297ae1a638de51dfc319fca41 Mon Sep 17 00:00:00 2001 From: Yandiev Ruslan Date: Thu, 16 Dec 2021 00:21:14 +0300 Subject: [PATCH 11/44] Fixed issues with `Keyboard::typeText` with multibyte strings (#304) Co-authored-by: Graham Campbell --- composer.json | 1 + src/Input/Keyboard.php | 4 ++-- tests/KeyboardApiTest.php | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e1c8a0c7..05cdd059 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "monolog/monolog": "^1.26 || ^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", "symfony/filesystem": "^4.4 || ^5.0 || ^6.0", + "symfony/polyfill-mbstring": "^1.23", "symfony/process": "^4.4 || ^5.0 || ^6.0" }, "require-dev":{ diff --git a/src/Input/Keyboard.php b/src/Input/Keyboard.php index e672e80e..766f82d6 100644 --- a/src/Input/Keyboard.php +++ b/src/Input/Keyboard.php @@ -50,13 +50,13 @@ public function typeText(string $text) { $this->page->assertNotClosed(); - $length = \strlen($text); + $length = \mb_strlen($text); for ($i = 0; $i < $length; ++$i) { $this->page->getSession()->sendMessageSync(new Message('Input.dispatchKeyEvent', [ 'type' => 'char', 'modifiers' => $this->getModifiers(), - 'text' => $text[$i], + 'text' => \mb_substr($text, $i, 1), ])); \usleep($this->sleep); diff --git a/tests/KeyboardApiTest.php b/tests/KeyboardApiTest.php index 0f817788..4fa6c37b 100644 --- a/tests/KeyboardApiTest.php +++ b/tests/KeyboardApiTest.php @@ -169,4 +169,28 @@ public function testKeyInterval(): void // if this test takes less than 300ms to run (3 keys x 100ms), setKeyInterval is not working $this->assertGreaterThan(300, $millisecondsElapsed); } + + /** + * @throws \HeadlessChromium\Exception\CommunicationException + * @throws \HeadlessChromium\Exception\NoResponseAvailable + * @throws \HeadlessChromium\Exception\CommunicationException\InvalidResponse + */ + public function testTypeUnicodeText(): void + { + // initial navigation + $page = $this->openSitePage('form.html'); + + $text = 'Со ГӀалгӀа ва'; + + $page->keyboard() + ->type('Tab') + ->typeText($text); + + $value = $page + ->evaluate('document.querySelector("#myinput").value;') + ->getReturnValue(); + + // checks if the input contains the typed text + $this->assertSame($text, $value); + } } From 01ad1828b3b9184bce4b53fa7f2b84b7897d6d78 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 20 Jan 2022 18:05:11 +0100 Subject: [PATCH 12/44] Fix grammar in contributing docs --- .github/CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8388d2ec..294ffb34 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,10 +8,10 @@ Issues Reporting an issue is the foremost way to contribute. -When reporting an issue, try to give as much details as possible. Ability to reproduce the issue is a priority. +When reporting an issue, try to give as many details as possible. Ability to reproduce the issue is a priority. If you give an example that reproduces your issue you will have more chances to get it fixed. -Those details include: +Those details include: - php version - chrome version @@ -33,20 +33,20 @@ You will be provided with an output of what is happening within the library and Tests ----- -Writting test is also a great way to contribute because it ensures that the library will remain consistent after any upgrade. +Writing test is also a great way to contribute because it ensures that the library will remain consistent after any upgrade. Implementing new features or fixing bugs ---------------------------------------- Implementing new features will allow anyone to take profit of your work. Just remember to rise an issue and discuss it before to make sure that the work goes in the right direction and you work will be approved. -In addition all contributions must be tested following as much as possible the current test structure: +In addition, all contributions must be tested following as much as possible the current test structure: - One class = one test file in ``test/suites`` and the class must be annotated with ``@covers``. - One class method = one method in the test class. Look at current tests in ``test/suites`` for more details. -Writting documentation +Writing documentation ---------------------- We encourage anyone to improve the documentation by adding new example or by fixing current one that would be wrong or outdated. From 7ebba25dbe0a322464bf08293bf7f50b1dd4d82e Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 21 Jan 2022 17:58:47 +0100 Subject: [PATCH 13/44] Use PHPUnit's `assertMatchesRegularExpression()` --- tests/BrowserFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/BrowserFactoryTest.php b/tests/BrowserFactoryTest.php index c125ed93..9098ca7c 100644 --- a/tests/BrowserFactoryTest.php +++ b/tests/BrowserFactoryTest.php @@ -26,7 +26,7 @@ public function testBrowserFactory(): void $browser = $factory->createBrowser(); - $this->assertRegExp('#^ws://#', $browser->getSocketUri()); + $this->assertMatchesRegularExpression('#^ws://#', $browser->getSocketUri()); } public function testWindowSizeOption(): void From 5f0be27e83e0448c452ba06d8f0927a9b71025d7 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 21 Jan 2022 19:12:12 +0100 Subject: [PATCH 14/44] Drop "go to a" from bigLayout as it might accidentally make tests testing big layouts succeed in redirecting (this is not happening anywhere AFAIK *yet*) --- tests/resources/static-web/bigLayout.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/resources/static-web/bigLayout.html b/tests/resources/static-web/bigLayout.html index da910030..7189c98e 100644 --- a/tests/resources/static-web/bigLayout.html +++ b/tests/resources/static-web/bigLayout.html @@ -12,8 +12,6 @@

page b

b
-go to a - From 97350d35af820f918c49f9a94a3bceac8d875248 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 21 Jan 2022 18:36:38 +0100 Subject: [PATCH 15/44] Round x y to prevent implicit int conversion --- tests/MouseApiTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MouseApiTest.php b/tests/MouseApiTest.php index 93895574..98e09c60 100644 --- a/tests/MouseApiTest.php +++ b/tests/MouseApiTest.php @@ -68,7 +68,7 @@ public function testClickLink(): void ->evaluate('JSON.parse(JSON.stringify(document.querySelector("#a").getBoundingClientRect()));') ->getReturnValue(); - $page->mouse()->move($rect['x'], $rect['y'])->click(); + $page->mouse()->move(\ceil($rect['x']), \ceil($rect['y']))->click(); $page->waitForReload(); $title = $page->evaluate('document.title')->getReturnValue(); From a8aeb55fbbd25a005c70d50ebe3772871546e066 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 22 Jan 2022 13:46:48 +0100 Subject: [PATCH 16/44] Make it work with retina (#325) I have added a way to read css metrics that are the same as standard ones except that they consider retina displays. Now the lib works in headful mode on retina displays. --- src/Input/Mouse.php | 12 +++--- src/Page.php | 2 +- src/PageUtils/PageLayoutMetrics.php | 58 +++++++++++++++++++++---- tests/PageTest.php | 67 +++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/src/Input/Mouse.php b/src/Input/Mouse.php index 663de90e..768125b1 100644 --- a/src/Input/Mouse.php +++ b/src/Input/Mouse.php @@ -178,11 +178,11 @@ private function scroll(int $distanceY, int $distanceX = 0): self { $this->page->assertNotClosed(); - $scollableArea = $this->page->getLayoutMetrics()->getContentSize(); - $visibleArea = $this->page->getLayoutMetrics()->getVisualViewport(); + $scrollableArea = $this->page->getLayoutMetrics()->getCssContentSize(); + $visibleArea = $this->page->getLayoutMetrics()->getCssVisualViewport(); - $distanceX = $this->getMaximumDistance($distanceX, $visibleArea['pageX'], $scollableArea['width']); - $distanceY = $this->getMaximumDistance($distanceY, $visibleArea['pageY'], $scollableArea['height']); + $distanceX = $this->getMaximumDistance($distanceX, $visibleArea['pageX'], $scrollableArea['width']); + $distanceY = $this->getMaximumDistance($distanceY, $visibleArea['pageY'], $scrollableArea['height']); $targetX = $visibleArea['pageX'] + $distanceX; $targetY = $visibleArea['pageY'] + $distanceY; @@ -222,7 +222,7 @@ private function scroll(int $distanceY, int $distanceX = 0): self */ private function scrollToBoundary(int $right, int $bottom): self { - $visibleArea = $this->page->getLayoutMetrics()->getLayoutViewport(); + $visibleArea = $this->page->getLayoutMetrics()->getCssLayoutViewport(); $distanceX = $distanceY = 0; @@ -338,7 +338,7 @@ private function getMaximumDistance(int $distance, int $current, int $maximum): private function waitForScroll(int $targetX, int $targetY) { while (true) { - $visibleArea = $this->page->getLayoutMetrics()->getVisualViewport(); + $visibleArea = $this->page->getLayoutMetrics()->getCssVisualViewport(); if ($visibleArea['pageX'] === $targetX && $visibleArea['pageY'] === $targetY) { return true; diff --git a/src/Page.php b/src/Page.php index 0efe41a8..ff9e6307 100644 --- a/src/Page.php +++ b/src/Page.php @@ -461,7 +461,7 @@ private function waitForReloadGenerator($eventName, $loaderId) */ public function getFullPageClip(int $timeout = null): Clip { - $contentSize = $this->getLayoutMetrics()->await($timeout)->getContentSize(); + $contentSize = $this->getLayoutMetrics()->await($timeout)->getCssContentSize(); return new Clip(0, 0, $contentSize['width'], $contentSize['height']); } diff --git a/src/PageUtils/PageLayoutMetrics.php b/src/PageUtils/PageLayoutMetrics.php index a8718398..54eb94cb 100644 --- a/src/PageUtils/PageLayoutMetrics.php +++ b/src/PageUtils/PageLayoutMetrics.php @@ -47,9 +47,7 @@ public function getMetrics(): array */ public function getContentSize(): array { - $response = $this->awaitResponse(); - - return $response->getResultData('contentSize'); + return $this->getResultData('contentSize'); } /** @@ -63,9 +61,7 @@ public function getContentSize(): array */ public function getLayoutViewport(): array { - $response = $this->awaitResponse(); - - return $response->getResultData('layoutViewport'); + return $this->getResultData('layoutViewport'); } /** @@ -79,8 +75,54 @@ public function getLayoutViewport(): array */ public function getVisualViewport() { - $response = $this->awaitResponse(); + return $this->getResultData('visualViewport'); + } + + /** + * Returns real size of scrollable area. + * + * @throws CommunicationException\ResponseHasError + * @throws \HeadlessChromium\Exception\NoResponseAvailable + * @throws \HeadlessChromium\Exception\OperationTimedOut + * + * @return array + */ + public function getCssContentSize(): array + { + return $this->getResultData('cssContentSize'); + } + + /** + * Returns real metrics relating to the layout viewport. + * + * @throws CommunicationException\ResponseHasError + * @throws \HeadlessChromium\Exception\NoResponseAvailable + * @throws \HeadlessChromium\Exception\OperationTimedOut + * + * @return array + */ + public function getCssLayoutViewport(): array + { + return $this->getResultData('cssLayoutViewport'); + } + + /** + * Returns real metrics relating to the visual viewport. + * + * @throws CommunicationException\ResponseHasError + * @throws \HeadlessChromium\Exception\NoResponseAvailable + * @throws \HeadlessChromium\Exception\OperationTimedOut + * + * @return array + */ + public function getCssVisualViewport() + { + return $this->getResultData('cssVisualViewport'); + } - return $response->getResultData('visualViewport'); + /** @param 'layoutViewport'|'visualViewport'|'contentSize'|'cssLayoutViewport'|'cssVisualViewport'|'cssContentSize' $key */ + private function getResultData(string $key): array + { + return $this->awaitResponse()->getResultData($key); } } diff --git a/tests/PageTest.php b/tests/PageTest.php index f7670e67..68609388 100644 --- a/tests/PageTest.php +++ b/tests/PageTest.php @@ -263,6 +263,9 @@ public function testGetLayoutMetrics(): void $contentSize = $metrics->getContentSize(); $layoutViewport = $metrics->getLayoutViewport(); $visualViewport = $metrics->getVisualViewport(); + $cssContentSize = $metrics->getCssContentSize(); + $cssLayoutViewport = $metrics->getCssLayoutViewport(); + $cssVisualViewport = $metrics->getCssVisualViewport(); $this->assertEquals( [ @@ -297,6 +300,70 @@ public function testGetLayoutMetrics(): void ], $visualViewport ); + + // This is made to be a bit loose to pass on retina displays + + $this->assertContains( + $cssContentSize, + [ + [ + 'x' => 0, + 'y' => 0, + 'width' => 900, + 'height' => 1000, + ], + [ + 'x' => 0, + 'y' => 0, + 'width' => 1800, + 'height' => 2000, + ], + ] + ); + + $this->assertContains( + $cssLayoutViewport, + [ + [ + 'pageX' => 0, + 'pageY' => 0, + 'clientWidth' => 100, + 'clientHeight' => 300, + ], + [ + 'pageX' => 0, + 'pageY' => 0, + 'clientWidth' => 200, + 'clientHeight' => 600, + ], + ] + ); + + $this->assertContains( + $cssVisualViewport, + [ + [ + 'offsetX' => 0, + 'offsetY' => 0, + 'pageX' => 0, + 'pageY' => 0, + 'clientWidth' => 100, + 'clientHeight' => 300, + 'scale' => 1, + 'zoom' => 1, + ], + [ + 'offsetX' => 0, + 'offsetY' => 0, + 'pageX' => 0, + 'pageY' => 0, + 'clientWidth' => 200, + 'clientHeight' => 600, + 'scale' => 1, + 'zoom' => 1, + ], + ] + ); } public function testGetFullPageClip(): void From a5e2d293fbc79fbc7d5bec858021c662f9e34a8f Mon Sep 17 00:00:00 2001 From: Karl Pierce Date: Sat, 22 Jan 2022 05:00:48 -0800 Subject: [PATCH 17/44] Add support for --no-proxy-server and --proxy-bypass-list Chrome args (#312) * Add support for --no-proxy-server and --proxy-bypass-list Chrome args Co-authored-by: Karl Pierce --- README.md | 2 ++ src/Browser/BrowserProcess.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index b43ab055..7b606dd0 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,8 @@ Here are the options available for the browser factory: | `ignoreCertificateErrors` | `false` | Set chrome to ignore ssl errors | | `keepAlive` | `false` | Set to `true` to keep alive the chrome instance when the script terminates | | `noSandbox` | `false` | Enable no sandbox mode, useful to run in a docker container | +| `noProxyServer` | `false` | Don't use a proxy server, always make direct connections. Overrides other proxy settings. | +| `proxyBypassList` | none | Specifies a list of hosts for whom we bypass proxy settings and use direct connections | | `proxyServer` | none | Proxy server to use. usage: `127.0.0.1:8080` (authorisation with credentials does not work) | | `sendSyncDefaultTimeout` | `5000` | Default timeout (ms) for sending sync messages | | `startupTimeout` | `30` | Maximum time in seconds to wait for chrome to start | diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index dc6ca619..70fddff6 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -362,6 +362,12 @@ private function getArgsFromOptions($binary, array $options) if (\array_key_exists('proxyServer', $options)) { $args[] = '--proxy-server='.$options['proxyServer']; } + if (\array_key_exists('noProxyServer', $options) && $options['noProxyServer']) { + $args[] = '--no-proxy-server'; + } + if (\array_key_exists('proxyBypassList', $options)) { + $args[] = '--proxy-bypass-list='.$options['proxyBypassList']; + } // add custom flags if (\array_key_exists('customFlags', $options) && \is_array($options['customFlags'])) { From e06158206a2ce0e5fbe72265d7da1b5d3e6a5429 Mon Sep 17 00:00:00 2001 From: Enrico Dias <32619307+enricodias@users.noreply.github.com> Date: Sat, 22 Jan 2022 12:38:20 -0300 Subject: [PATCH 18/44] Replace microtime with hrtime (#318) * Replace microtime with hrtime The system clock shouldn't interfere with the calculation of timeouts. * Cleanup * Update OperationTimedOut.php Co-authored-by: Graham Campbell --- src/Communication/Connection.php | 4 ++-- src/Exception/OperationTimedOut.php | 17 +++++++++++++++++ src/Utils.php | 18 +++++------------- tests/KeyboardApiTest.php | 4 ++-- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Communication/Connection.php b/src/Communication/Connection.php index 917e367c..c42accfb 100644 --- a/src/Communication/Connection.php +++ b/src/Communication/Connection.php @@ -212,7 +212,7 @@ public function isConnected() private function waitForDelay(): void { if ($this->lastMessageSentTime) { - $currentTime = (int) (\microtime(true) * 1000); + $currentTime = (int) (\hrtime(true) / 1000 / 1000); // if not enough time was spent until last message was sent, wait if ($this->lastMessageSentTime + $this->delay > $currentTime) { $timeToWait = ($this->lastMessageSentTime + $this->delay) - $currentTime; @@ -220,7 +220,7 @@ private function waitForDelay(): void } } - $this->lastMessageSentTime = (int) (\microtime(true) * 1000); + $this->lastMessageSentTime = (int) (\hrtime(true) / 1000 / 1000); } /** diff --git a/src/Exception/OperationTimedOut.php b/src/Exception/OperationTimedOut.php index 73b569c7..4c9b52ee 100644 --- a/src/Exception/OperationTimedOut.php +++ b/src/Exception/OperationTimedOut.php @@ -13,4 +13,21 @@ class OperationTimedOut extends \Exception { + public static function createFromTimeout(int $timeoutMicroSec): self + { + return new self(sprintf('Operation timed out after %s.', self::getTimeoutPhrase($timeoutMicroSec))); + } + + private static function getTimeoutPhrase(int $timeoutMicroSec): string + { + if ($timeoutMicroSec > 1000 * 1000) { + return sprintf('%ds', (int) ($timeoutMicroSec / (1000 * 1000))); + } + + if ($timeoutMicroSec > 1000) { + return sprintf('%dms', (int) ($timeoutMicroSec / 1000)); + } + + return sprintf('%dμs', (int) ($timeoutMicroSec)); + } } diff --git a/src/Utils.php b/src/Utils.php index c2121410..b97887f9 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -52,27 +52,19 @@ class Utils */ public static function tryWithTimeout(int $timeoutMicroSec, \Generator $generator, callable $onTimeout = null) { - $waitUntilMicroSec = \microtime(true) * 1000 * 1000 + $timeoutMicroSec; + $waitUntilMicroSec = \hrtime(true) / 1000 + $timeoutMicroSec; foreach ($generator as $v) { // if timeout reached or if time+delay exceed timeout stop the execution - if (\microtime(true) * 1000 * 1000 + $v >= $waitUntilMicroSec) { - if ($onTimeout) { + if (\hrtime(true) / 1000 + (int) $v >= $waitUntilMicroSec) { + if (null !== $onTimeout) { // if callback was set execute it return $onTimeout(); - } else { - if ($timeoutMicroSec > 1000 * 1000) { - $timeoutPhrase = (int) ($timeoutMicroSec / (1000 * 1000)).'sec'; - } elseif ($timeoutMicroSec > 1000) { - $timeoutPhrase = (int) ($timeoutMicroSec / 1000).'ms'; - } else { - $timeoutPhrase = (int) ($timeoutMicroSec).'μs'; - } - throw new OperationTimedOut('Operation timed out ('.$timeoutPhrase.')'); } + throw OperationTimedOut::createFromTimeout($timeoutMicroSec); } - \usleep($v); + \usleep((int) $v); } return $generator->getReturn(); diff --git a/tests/KeyboardApiTest.php b/tests/KeyboardApiTest.php index 4fa6c37b..fd770e31 100644 --- a/tests/KeyboardApiTest.php +++ b/tests/KeyboardApiTest.php @@ -157,14 +157,14 @@ public function testKeyInterval(): void // initial navigation $page = $this->openSitePage('form.html'); - $start = \round(\microtime(true) * 1000); + $start = \round(\hrtime(true) / 1000 / 1000); $page->keyboard() ->setKeyInterval(100) ->typeRawKey('Tab') ->typeText('bar'); - $millisecondsElapsed = \round(\microtime(true) * 1000) - $start; + $millisecondsElapsed = \round(\hrtime(true) / 1000 / 1000) - $start; // if this test takes less than 300ms to run (3 keys x 100ms), setKeyInterval is not working $this->assertGreaterThan(300, $millisecondsElapsed); From 946403073185b6223b7eae51918adee797a7a203 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 22 Jan 2022 15:38:34 +0000 Subject: [PATCH 19/44] Apply fixes from StyleCI --- src/Exception/OperationTimedOut.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Exception/OperationTimedOut.php b/src/Exception/OperationTimedOut.php index 4c9b52ee..8e4e5bc0 100644 --- a/src/Exception/OperationTimedOut.php +++ b/src/Exception/OperationTimedOut.php @@ -15,19 +15,19 @@ class OperationTimedOut extends \Exception { public static function createFromTimeout(int $timeoutMicroSec): self { - return new self(sprintf('Operation timed out after %s.', self::getTimeoutPhrase($timeoutMicroSec))); + return new self(\sprintf('Operation timed out after %s.', self::getTimeoutPhrase($timeoutMicroSec))); } private static function getTimeoutPhrase(int $timeoutMicroSec): string { if ($timeoutMicroSec > 1000 * 1000) { - return sprintf('%ds', (int) ($timeoutMicroSec / (1000 * 1000))); + return \sprintf('%ds', (int) ($timeoutMicroSec / (1000 * 1000))); } if ($timeoutMicroSec > 1000) { - return sprintf('%dms', (int) ($timeoutMicroSec / 1000)); + return \sprintf('%dms', (int) ($timeoutMicroSec / 1000)); } - return sprintf('%dμs', (int) ($timeoutMicroSec)); + return \sprintf('%dμs', (int) ($timeoutMicroSec)); } } From bd3d51a8e22b83db7e2ccd69c7498a267c25ee5f Mon Sep 17 00:00:00 2001 From: Enrico Dias <32619307+enricodias@users.noreply.github.com> Date: Sat, 22 Jan 2022 12:39:03 -0300 Subject: [PATCH 20/44] Add `Node::sendFiles()` (#317) --- src/Dom/Node.php | 7 ++++++- tests/DomTest.php | 23 +++++++++++++++++++++-- tests/resources/static-web/domForm.html | 1 + 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Dom/Node.php b/src/Dom/Node.php index d284750f..2a63a473 100644 --- a/src/Dom/Node.php +++ b/src/Dom/Node.php @@ -179,9 +179,14 @@ public function sendKeys(string $text): void } public function sendFile(string $filePath): void + { + $this->sendFiles([$filePath]); + } + + public function sendFiles(array $filePaths): void { $message = new Message('DOM.setFileInputFiles', [ - 'files' => [$filePath], + 'files' => $filePaths, 'nodeId' => $this->nodeId, ]); $response = $this->page->getSession()->sendMessageSync($message); diff --git a/tests/DomTest.php b/tests/DomTest.php index 232174bf..fefcd5a8 100644 --- a/tests/DomTest.php +++ b/tests/DomTest.php @@ -149,7 +149,26 @@ public function testUploadFile(): void ->evaluate('document.querySelector("#myfile").value;') ->getReturnValue(); - // check if file was uploaded - $this->assertNotEmpty($value); + // check if the file was selected + $this->assertStringEndsWith(\basename($file), $value); + } + + public function testUploadFiles(): void + { + $page = $this->openSitePage('domForm.html'); + $files = [ + self::sitePath('domForm.html'), + self::sitePath('form.html'), + ]; + + $element = $page->dom()->querySelector('#myfiles'); + $element->sendFiles($files); + + $value1 = $page->evaluate('document.querySelector("#myfiles").files[0].name;')->getReturnValue(); + $value2 = $page->evaluate('document.querySelector("#myfiles").files[1].name;')->getReturnValue(); + + // check if the files were selected + $this->assertStringEndsWith(\basename($files[0]), $value1); + $this->assertStringEndsWith(\basename($files[1]), $value2); } } diff --git a/tests/resources/static-web/domForm.html b/tests/resources/static-web/domForm.html index ea80b0d7..7eff87f1 100644 --- a/tests/resources/static-web/domForm.html +++ b/tests/resources/static-web/domForm.html @@ -10,6 +10,7 @@

Form

+
From b3b80fd653e2fa53f932c2708fa2f8018ab104b4 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 22 Jan 2022 16:42:11 +0100 Subject: [PATCH 21/44] Fix `Mouse::find()` after cursor has moved (#320) * Add failing test * Fix `Mouse::find()` after cursor has moved After scrolling to the element, its boundaries change and we have to read the viewport offsets in order to compute final coordinates. * Update Mouse.php Co-authored-by: Graham Campbell --- src/Input/Mouse.php | 18 ++++++++++-------- tests/MouseApiTest.php | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Input/Mouse.php b/src/Input/Mouse.php index 768125b1..6f50dd76 100644 --- a/src/Input/Mouse.php +++ b/src/Input/Mouse.php @@ -285,14 +285,16 @@ public function find(string $selectors, int $position = 1): self $rightBoundary = \floor($element['right']); $bottomBoundary = \floor($element['bottom']); - $positionX = \mt_rand(\ceil($element['left']), $rightBoundary); - $positionY = \mt_rand(\ceil($element['top']), $bottomBoundary); - - $this->scrollToBoundary($rightBoundary, $bottomBoundary) - ->move( - ($positionX - $this->x), - ($positionY - $this->y) - ); + $this->scrollToBoundary($rightBoundary, $bottomBoundary); + + $visibleArea = $this->page->getLayoutMetrics()->getLayoutViewport(); + + $offsetX = $visibleArea['pageX']; + $offsetY = $visibleArea['pageY']; + $positionX = \random_int(\ceil($element['left'] - $offsetX), $rightBoundary - $offsetX); + $positionY = \random_int(\ceil($element['top'] - $offsetY), $bottomBoundary - $offsetY); + + $this->move($positionX, $positionY); return $this; } diff --git a/tests/MouseApiTest.php b/tests/MouseApiTest.php index 98e09c60..8fca0b04 100644 --- a/tests/MouseApiTest.php +++ b/tests/MouseApiTest.php @@ -117,6 +117,25 @@ public function testFind_withSingleElement(): void $this->assertEquals('a - test', $title); } + /** + * @throws \HeadlessChromium\Exception\CommunicationException + * @throws \HeadlessChromium\Exception\NoResponseAvailable + */ + public function testFind_afterMove(): void + { + // initial navigation + $page = $this->openSitePage('b.html'); + + $page->mouse()->move(1000, 1000); + + $page->mouse()->find('#a')->click(); + $page->waitForReload(); + + $title = $page->evaluate('document.title')->getReturnValue(); + + $this->assertEquals('a - test', $title); + } + /** * @dataProvider mouseFindProvider * From 3020ec67f3bd5e7362a2f2e990a7d916b708fdef Mon Sep 17 00:00:00 2001 From: Enrico Dias <32619307+enricodias@users.noreply.github.com> Date: Sat, 22 Jan 2022 12:42:56 -0300 Subject: [PATCH 22/44] Add timeout option to `Page::getHtml` (#316) --- src/Page.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Page.php b/src/Page.php index 0efe41a8..7fb87bd6 100644 --- a/src/Page.php +++ b/src/Page.php @@ -812,9 +812,9 @@ public function getCurrentUrl() * * @return string */ - public function getHtml() + public function getHtml(?int $timeout = null): string { - return $this->evaluate('document.documentElement.outerHTML')->getReturnValue(); + return $this->evaluate('document.documentElement.outerHTML')->getReturnValue($timeout); } /** From a03581b2d99d5e8cad413932d5cb4130717040c2 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sat, 22 Jan 2022 16:35:53 +0000 Subject: [PATCH 23/44] Revert "Add support for --no-proxy-server and --proxy-bypass-list Chrome args (#312)" (#327) This reverts commit a5e2d293fbc79fbc7d5bec858021c662f9e34a8f. --- README.md | 2 -- src/Browser/BrowserProcess.php | 6 ------ 2 files changed, 8 deletions(-) diff --git a/README.md b/README.md index 7b606dd0..b43ab055 100644 --- a/README.md +++ b/README.md @@ -169,8 +169,6 @@ Here are the options available for the browser factory: | `ignoreCertificateErrors` | `false` | Set chrome to ignore ssl errors | | `keepAlive` | `false` | Set to `true` to keep alive the chrome instance when the script terminates | | `noSandbox` | `false` | Enable no sandbox mode, useful to run in a docker container | -| `noProxyServer` | `false` | Don't use a proxy server, always make direct connections. Overrides other proxy settings. | -| `proxyBypassList` | none | Specifies a list of hosts for whom we bypass proxy settings and use direct connections | | `proxyServer` | none | Proxy server to use. usage: `127.0.0.1:8080` (authorisation with credentials does not work) | | `sendSyncDefaultTimeout` | `5000` | Default timeout (ms) for sending sync messages | | `startupTimeout` | `30` | Maximum time in seconds to wait for chrome to start | diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index 70fddff6..dc6ca619 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -362,12 +362,6 @@ private function getArgsFromOptions($binary, array $options) if (\array_key_exists('proxyServer', $options)) { $args[] = '--proxy-server='.$options['proxyServer']; } - if (\array_key_exists('noProxyServer', $options) && $options['noProxyServer']) { - $args[] = '--no-proxy-server'; - } - if (\array_key_exists('proxyBypassList', $options)) { - $args[] = '--proxy-bypass-list='.$options['proxyBypassList']; - } // add custom flags if (\array_key_exists('customFlags', $options) && \is_array($options['customFlags'])) { From b1d645b109f678fb0f42b0a0ba83838ebbf7508e Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sat, 22 Jan 2022 16:37:05 +0000 Subject: [PATCH 24/44] Revert "Revert "Add support for --no-proxy-server and --proxy-bypass-list Chrome args (#312)" (#327)" (#329) This reverts commit a03581b2d99d5e8cad413932d5cb4130717040c2. --- README.md | 2 ++ src/Browser/BrowserProcess.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index b43ab055..7b606dd0 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,8 @@ Here are the options available for the browser factory: | `ignoreCertificateErrors` | `false` | Set chrome to ignore ssl errors | | `keepAlive` | `false` | Set to `true` to keep alive the chrome instance when the script terminates | | `noSandbox` | `false` | Enable no sandbox mode, useful to run in a docker container | +| `noProxyServer` | `false` | Don't use a proxy server, always make direct connections. Overrides other proxy settings. | +| `proxyBypassList` | none | Specifies a list of hosts for whom we bypass proxy settings and use direct connections | | `proxyServer` | none | Proxy server to use. usage: `127.0.0.1:8080` (authorisation with credentials does not work) | | `sendSyncDefaultTimeout` | `5000` | Default timeout (ms) for sending sync messages | | `startupTimeout` | `30` | Maximum time in seconds to wait for chrome to start | diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index dc6ca619..70fddff6 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -362,6 +362,12 @@ private function getArgsFromOptions($binary, array $options) if (\array_key_exists('proxyServer', $options)) { $args[] = '--proxy-server='.$options['proxyServer']; } + if (\array_key_exists('noProxyServer', $options) && $options['noProxyServer']) { + $args[] = '--no-proxy-server'; + } + if (\array_key_exists('proxyBypassList', $options)) { + $args[] = '--proxy-bypass-list='.$options['proxyBypassList']; + } // add custom flags if (\array_key_exists('customFlags', $options) && \is_array($options['customFlags'])) { From 4ed67f37cb2e4a46d9e7fa9f992b1c2bf96baa34 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:50:46 +0000 Subject: [PATCH 25/44] Update LICENSE --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index c0babdd7..034561bd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ The MIT License (MIT) Copyright (c) 2017-2020 Soufiane Ghzal -Copyright (c) 2020-2021 Graham Campbell -Copyright (c) 2020-2021 Enrico Dias +Copyright (c) 2020-2022 Graham Campbell +Copyright (c) 2020-2022 Enrico Dias Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c68024c1b5832ddcc4ac16065379e2380c707187 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:54:05 +0000 Subject: [PATCH 26/44] Release 1.3.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e02cee03..301b64c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # CHANGELOG +## 1.3.1 (2022-01-23) + +* Fixed issues with `Keyboard::typeText` with multibyte strings +* Fixed issues with retina and scaled displays +* Fixed issues with timeouts if system time changes +* Fixed `Mouse::find()` after cursor has moved + ## 1.3.0 (2021-12-07) * Added support for setting HTTP headers From cf66cdb88a2e997ceff8667a1d14db5f0256c2bd Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:56:39 +0000 Subject: [PATCH 27/44] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301b64c9..710aa107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # CHANGELOG +## 1.4.0 (UPCOMING) + +* Added support for `--no-proxy-server` and `--proxy-bypass-list` + + ## 1.3.1 (2022-01-23) * Fixed issues with `Keyboard::typeText` with multibyte strings @@ -8,6 +13,7 @@ * Fixed issues with timeouts if system time changes * Fixed `Mouse::find()` after cursor has moved + ## 1.3.0 (2021-12-07) * Added support for setting HTTP headers From 433cde4373d3851b66fff5f89601ff12ebf9b5da Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:56:42 +0000 Subject: [PATCH 28/44] Update phpunit.xml.dist --- phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1747e869..59691007 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,6 +4,7 @@ bootstrap="vendor/autoload.php" forceCoversAnnotation="true" colors="true" + convertDeprecationsToExceptions="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" From 6de0210cffa45c5fb2710c6282cbcb40b608aa0f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:57:42 +0000 Subject: [PATCH 29/44] Update CODE_OF_CONDUCT.md --- .github/CODE_OF_CONDUCT.md | 137 +++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 29 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 5c1108f2..590c9593 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,53 +1,132 @@ -# Contributor Covenant Code of Conduct - -TLDL; Saying "hi", "please", "thanks" and being polite and showing empathy has never hurt anyone and makes everyone happy. -We are humans not robots, so let's make the open source experience an enjoyable one for humans that take part in it. +# CONTRIBUTOR COVENANT CODE OF CONDUCT ## Our Pledge -In the interest of fostering an open and welcoming environment, -we as contributors and maintainers pledge to making participation in our project and our community -a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, -gender identity and expression, level of experience, nationality, personal appearance, race, religion, -or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sghzal@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hello@gjcampbell.co.uk. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations From 757e8c5a83d1436b86d666a6b96e9357163ca4dd Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 23 Jan 2022 23:59:21 +0000 Subject: [PATCH 30/44] Release 1.4.0 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 710aa107..2702f6b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # CHANGELOG -## 1.4.0 (UPCOMING) +## 1.4.0 (2022-01-23) * Added support for `--no-proxy-server` and `--proxy-bypass-list` +* Added timeout option to `Page::getHtml` +* Added `Node::sendFiles` method ## 1.3.1 (2022-01-23) From bfab249181719bc32c09a958c8042193a4331142 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 25 Jan 2022 16:05:06 +0100 Subject: [PATCH 31/44] Add fallback to css layout metrics (#336) --- src/PageUtils/PageLayoutMetrics.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PageUtils/PageLayoutMetrics.php b/src/PageUtils/PageLayoutMetrics.php index 54eb94cb..3325ea32 100644 --- a/src/PageUtils/PageLayoutMetrics.php +++ b/src/PageUtils/PageLayoutMetrics.php @@ -89,7 +89,7 @@ public function getVisualViewport() */ public function getCssContentSize(): array { - return $this->getResultData('cssContentSize'); + return $this->getResultData('cssContentSize') ?? $this->getContentSize(); } /** @@ -103,7 +103,7 @@ public function getCssContentSize(): array */ public function getCssLayoutViewport(): array { - return $this->getResultData('cssLayoutViewport'); + return $this->getResultData('cssLayoutViewport') ?? $this->getLayoutViewport(); } /** @@ -117,7 +117,7 @@ public function getCssLayoutViewport(): array */ public function getCssVisualViewport() { - return $this->getResultData('cssVisualViewport'); + return $this->getResultData('cssVisualViewport') ?? $this->getVisualViewport(); } /** @param 'layoutViewport'|'visualViewport'|'contentSize'|'cssLayoutViewport'|'cssVisualViewport'|'cssContentSize' $key */ From b0f556515798058406b3f2004925af8e49cf4e67 Mon Sep 17 00:00:00 2001 From: momala454 Date: Tue, 25 Jan 2022 22:25:23 +0100 Subject: [PATCH 32/44] Fix missing destroyed (#338) * Fix missing destroyed * Update Session.php Co-authored-by: Graham Campbell --- src/Communication/Session.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Communication/Session.php b/src/Communication/Session.php index 28dec10e..d2d086b1 100644 --- a/src/Communication/Session.php +++ b/src/Communication/Session.php @@ -135,6 +135,7 @@ public function destroy(): void } $this->emit('destroyed'); $this->connection = null; + $this->destroyed = true; $this->removeAllListeners(); } } From a066c24c5dcbaa4bc3aa5bead410b5eccdc3986c Mon Sep 17 00:00:00 2001 From: Seriyyy95 Date: Thu, 27 Jan 2022 22:06:32 +0200 Subject: [PATCH 33/44] Prevent Node::querySelector from returning nodeId 0 (#326) * Update Node.php Fix bug with zero node id * Add tests for querySelector returns null --- src/Dom/Node.php | 2 +- tests/DomTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Dom/Node.php b/src/Dom/Node.php index 2a63a473..fb336245 100644 --- a/src/Dom/Node.php +++ b/src/Dom/Node.php @@ -64,7 +64,7 @@ public function querySelector(string $selector): ?self $nodeId = $response->getResultData('nodeId'); - if (null !== $nodeId) { + if (null !== $nodeId && 0 !== $nodeId) { return new self($this->page, $nodeId); } diff --git a/tests/DomTest.php b/tests/DomTest.php index fefcd5a8..10a7cafd 100644 --- a/tests/DomTest.php +++ b/tests/DomTest.php @@ -40,8 +40,10 @@ public function testSearchByCssSelector(): void { $page = $this->openSitePage('domForm.html'); $element = $page->dom()->querySelector('button'); + $notFoundElement = $page->dom()->querySelector('img'); $this->assertNotNull($element); + $this->assertNull($notFoundElement); } public function testSearchByCssSelectorAll(): void @@ -51,6 +53,9 @@ public function testSearchByCssSelectorAll(): void $elements = $page->dom()->querySelectorAll('div'); $this->assertCount(2, $elements); + + $notFoundElements = $page->dom()->querySelectorAll('img'); + $this->assertCount(0, $notFoundElements); } public function testSearchByXpath(): void From da3e4114bca80493fa8c49e7bf63dff77e89bc0b Mon Sep 17 00:00:00 2001 From: Aldo Mateli Date: Fri, 18 Feb 2022 13:37:12 +0100 Subject: [PATCH 34/44] Fix cookie expiration example (#345) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b606dd0..18637db5 100644 --- a/README.md +++ b/README.md @@ -577,7 +577,7 @@ $page = $browser->createPage(); $page->setCookies([ Cookie::create('name', 'value', [ 'domain' => 'example.com', - 'expires' => time() + 3600 // expires in 1 day + 'expires' => time() + 3600 // expires in 1 hour ]) ])->await(); From 72d5bc2a37c4ce56b6b7d90d8991149b814263c6 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 18 Feb 2022 14:27:58 +0000 Subject: [PATCH 35/44] Apply fixes from StyleCI --- src/Browser/BrowserProcess.php | 2 +- tests/Communication/ConnectionTest.php | 6 +++--- tests/HttpEnabledTestCase.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index 70fddff6..2a94414c 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -209,7 +209,7 @@ public function kill(): void $this->logger->debug('process: trying to close chrome gracefully'); $this->browser->sendCloseMessage(); } catch (\Exception $e) { - //log + // log $this->logger->debug('process: closing chrome gracefully - compatibility'); // close all pages if connected diff --git a/tests/Communication/ConnectionTest.php b/tests/Communication/ConnectionTest.php index 196f35cb..bafa66b7 100644 --- a/tests/Communication/ConnectionTest.php +++ b/tests/Communication/ConnectionTest.php @@ -209,7 +209,7 @@ public function testExceptionInvalideJson(): void $connection = new Connection($this->mocSocket); $connection->connect(); - //set invalid json + // set invalid json $this->mocSocket->addReceivedData('{'); $connection->readData(); @@ -222,7 +222,7 @@ public function testExceptionInvalideArrayResponse(): void $connection = new Connection($this->mocSocket); $connection->connect(); - //set string variable instead of array + // set string variable instead of array $this->mocSocket->addReceivedData('"foo"'); $connection->readData(); @@ -235,7 +235,7 @@ public function testInvalidResponseId(): void $connection = new Connection($this->mocSocket); $connection->connect(); - //set string variable instead of array + // set string variable instead of array $this->mocSocket->addReceivedData('{"message": "foo"}'); $connection->readData(); diff --git a/tests/HttpEnabledTestCase.php b/tests/HttpEnabledTestCase.php index 9a6e2092..823a7533 100644 --- a/tests/HttpEnabledTestCase.php +++ b/tests/HttpEnabledTestCase.php @@ -30,7 +30,7 @@ public static function setUpBeforeClass(): void __DIR__.'/resources/static-web', ]); self::$process->start(); - \usleep(80000); //wait for server to get going + \usleep(80000); // wait for server to get going // ensure it started if (!self::$process->isRunning()) { From 34c617f2a5cb07d9715c3f614479f88aaf0cc552 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 2 Mar 2022 14:55:53 +0000 Subject: [PATCH 36/44] Support Composer 2.2 --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 05cdd059..707551b0 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,9 @@ } }, "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, "preferred-install": "dist" } } From f1c4712154d74f1478a489c86f12161385176b59 Mon Sep 17 00:00:00 2001 From: Edgard Lorraine Messias Date: Wed, 2 Mar 2022 16:37:38 -0300 Subject: [PATCH 37/44] Fix to not open "What's new" page on startup (#350) --- src/Browser/BrowserProcess.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Browser/BrowserProcess.php b/src/Browser/BrowserProcess.php index 2a94414c..e01347f5 100644 --- a/src/Browser/BrowserProcess.php +++ b/src/Browser/BrowserProcess.php @@ -303,6 +303,7 @@ private function getArgsFromOptions($binary, array $options) '--disable-prompt-on-repost', '--disable-sync', '--disable-translate', + '--disable-features=ChromeWhatsNewUI', '--metrics-recording-only', '--no-first-run', '--safebrowsing-disable-auto-update', From 303d5be396dab1093eb38461540017a2ae31f419 Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Fri, 18 Mar 2022 21:18:39 +0200 Subject: [PATCH 38/44] Change uploadFile() to sendFile() in README.md (#356) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18637db5..aad6d8ba 100644 --- a/README.md +++ b/README.md @@ -729,7 +729,7 @@ $elem->sendKeys('Sample text'); You can upload file to file from the input: ```php -$elem->uploadFile('/path/to/file'); +$elem->sendFile('/path/to/file'); ``` You can get element text or attribute: From 01e0ed98073086ca38d9d56fac538e25a15ecfad Mon Sep 17 00:00:00 2001 From: Julien Dephix Date: Thu, 24 Mar 2022 16:11:02 +0100 Subject: [PATCH 39/44] Fix typo in README.md (#358) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aad6d8ba..68e70a41 100644 --- a/README.md +++ b/README.md @@ -457,7 +457,7 @@ header('Pragma: public'); echo base64_decode($pdf->getBase64()); ``` -Options `headerTempalte` and `footerTempalte`: +Options `headerTemplate` and `footerTemplate`: Should be valid HTML markup with the following classes used to inject printing values into them: - date: formatted print date From 8f837930d6e81043a3886d91ffb3b5494d44731a Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 24 Mar 2022 17:18:04 +0000 Subject: [PATCH 40/44] [1.4] PHP 8.1 iterator fixes (#360) --- src/Communication/Response.php | 2 -- src/Cookies/Cookie.php | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Communication/Response.php b/src/Communication/Response.php index dddc6a1f..a72ab13e 100644 --- a/src/Communication/Response.php +++ b/src/Communication/Response.php @@ -119,7 +119,6 @@ public function offsetGet($offset) /** * {@inheritdoc} */ - #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { throw new \Exception('Responses are immutable'); @@ -128,7 +127,6 @@ public function offsetSet($offset, $value): void /** * {@inheritdoc} */ - #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Exception('Responses are immutable'); diff --git a/src/Cookies/Cookie.php b/src/Cookies/Cookie.php index 37651d81..046fd687 100644 --- a/src/Cookies/Cookie.php +++ b/src/Cookies/Cookie.php @@ -57,6 +57,7 @@ public function getDomain() /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return \array_key_exists($offset, $this->data); @@ -65,6 +66,7 @@ public function offsetExists($offset) /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->data[$offset] ?? null; From 7fd0d68ecbbbc45cfcc0b8d2d0715dc0222ced50 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 24 Mar 2022 23:16:42 +0000 Subject: [PATCH 41/44] More PHP 8.1 fixes Closes #361 --- src/Cookies/CookiesCollection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Cookies/CookiesCollection.php b/src/Cookies/CookiesCollection.php index fcc1ca6f..23ba52c8 100644 --- a/src/Cookies/CookiesCollection.php +++ b/src/Cookies/CookiesCollection.php @@ -44,6 +44,7 @@ public function addCookie(Cookie $cookie): void /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->cookies); @@ -52,6 +53,7 @@ public function getIterator() /** * {@inheritdoc} */ + #[\ReturnTypeWillChange] public function count() { return \count($this->cookies); From 93a82e0ffb304a06adfa229cc596723ab4d2034c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 25 Mar 2022 08:35:24 +0000 Subject: [PATCH 42/44] Adjusted versions --- .github/workflows/static.yml | 2 +- .styleci.yml | 2 ++ vendor-bin/phpstan/composer.json | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index bff3e1b2..78addca8 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -16,7 +16,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.3' + php-version: '8.0' tools: composer:v2 coverage: none diff --git a/.styleci.yml b/.styleci.yml index a923f78e..a70b1b34 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,5 +1,7 @@ preset: symfony +version: 7.4 + risky: true enabled: diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json index ddf7edb3..c2a5310b 100644 --- a/vendor-bin/phpstan/composer.json +++ b/vendor-bin/phpstan/composer.json @@ -1,7 +1,7 @@ { "require": { - "php": "^7.3", - "phpstan/phpstan": "1.2.0" + "php": "^8.0", + "phpstan/phpstan": "1.5.0" }, "config": { "preferred-install": "dist" From 1fbe90ff78eb8d1c342c060c429df73cf0276e8b Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 25 Mar 2022 08:44:02 +0000 Subject: [PATCH 43/44] Switch to 7.4 --- .github/workflows/static.yml | 2 +- vendor-bin/phpstan/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 78addca8..c7f0e44e 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -16,7 +16,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '7.4' tools: composer:v2 coverage: none diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json index c2a5310b..6de6da3d 100644 --- a/vendor-bin/phpstan/composer.json +++ b/vendor-bin/phpstan/composer.json @@ -1,6 +1,6 @@ { "require": { - "php": "^8.0", + "php": "^7.4", "phpstan/phpstan": "1.5.0" }, "config": { From 7d6f044ec81042e72a94542f4e568abce4e01d7b Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 25 Mar 2022 08:51:36 +0000 Subject: [PATCH 44/44] Release 1.4.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2702f6b1..aa9e01d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # CHANGELOG +## 1.4.1 (2022-03-25) + +* Added fallback to css layout metrics +* Added missing destroyed setting +* Prevent `Node::querySelector` from returning nodeId `0` +* Fixed "What's new" page opening on startup +* More fixes to enable eventual PHP 8.1 support + + ## 1.4.0 (2022-01-23) * Added support for `--no-proxy-server` and `--proxy-bypass-list`