chrome-php / chrome

Instrument headless chrome/chromium instances from PHP
MIT License
2.25k stars 275 forks source link

Something is messed up on Windows.. #572

Open divinity76 opened 9 months ago

divinity76 commented 9 months ago

Have not found a good way to reproduce it, but basically, with valid login credentials, on Windows (but not Linux), there's like a 50% chance of the line

$loginError = $page->evaluate('123')->getReturnValue();

throwing a

PHP Fatal error:  Uncaught HeadlessChromium\Exception\OperationTimedOut: Operation timed out after 5s. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php:18
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Utils.php(67): HeadlessChromium\Exception\OperationTimedOut::createFromTimeout(5000000)
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\ResponseReader.php(114): HeadlessChromium\Utils::tryWithTimeout(5000000, Object(Generator))
#2 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(77): HeadlessChromium\Communication\ResponseReader->waitForResponse(5000)
#3 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(108): HeadlessChromium\PageUtils\PageEvaluation->waitForResponse(NULL)
#4 C:\cygwin64\home\hans\reproduce.php(70): HeadlessChromium\PageUtils\PageEvaluation->getReturnValue()
#5 C:\cygwin64\home\hans\reproduce.php(42): Harvest_Chromium->login()
#6 C:\cygwin64\home\hans\reproduce.php(76): Harvest_Chromium->__construct('C:\\Program File...', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#7 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php on line 18

in this code..

<?php

declare(strict_types=1);
init();
// <configuration>
// composer require chrome-php/chrome
$chromiumPath = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
$username = file_get_contents("harvest_username.txt");
$password = file_get_contents("harvest_password.txt");
// </configuration>
/// code below.
function init()
{
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
    ini_set('display_startup_errors', '1');
    set_error_handler(function ($errno, $errstr, $errfile, $errline) {
        if (error_reporting() & $errno) {
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
    });
    require_once 'vendor/autoload.php';
}

class Harvest_Chromium
{
    private \HeadlessChromium\Page $page;
    function __construct(
        private string $chromiumPath,
        #[\SensitiveParameter]
        private string $username,
        #[\SensitiveParameter]
        private string $password
    ) {
        $factory = new \HeadlessChromium\BrowserFactory($this->chromiumPath);
        $browser = $factory->createBrowser(
            [
                'headless' => false,
            ]
        );
        $this->page = $browser->createPage();
        $this->login();
    }
    function __destruct()
    {
        $this->page->close();
    }
    private function login(): void
    {
        $page = &$this->page;
        // https://id.getharvest.com/harvest/sign_in
        $page->navigate('https://id.getharvest.com/harvest/sign_in?')->waitForNavigation(
            \HeadlessChromium\Page::DOM_CONTENT_LOADED
        );
        // <input type="email" name="email" id="email" class="pds-input pds-input-lg" placeholder="Work email" autofocus="autofocus" autocapitalize="none" aria-label="Work email">
        $page->evaluate('document.querySelector("#email").value = ' . json_encode($this->username));
        // <input type="password" name="password" id="password" class="pds-input pds-input-lg" placeholder="Password" aria-label="Password">
        $page->evaluate('document.querySelector("#password").value = ' . json_encode($this->password));
        // <button name="button" type="submit" class="pds-button pds-button-primary pds-button-lg pds-w-full" id="log-in">Sign in</button>
        $page->evaluate('document.querySelector("#log-in").click()');
        // wait for load
        $page->waitForReload(\HeadlessChromium\Page::DOM_CONTENT_LOADED);
        /*
                login failed:
                <li role="alert" class="pds-alert alert pds-mb-md">
                          <span>Incorrect email or password.</span>
                          <button type="button" class="pds-alert-close js-dismiss-flash" aria-label="Dismiss Alert">Dismiss</button>
                        </li>
        */
        $loginError = $page->evaluate('123')->getReturnValue();
        var_dump($loginError);
        // login success!
        return;
    }
};
$o = new Harvest_Chromium($chromiumPath, $username, $password);

and I've run the same code many times on Ubuntu, it does not happen in Ubuntu at all, it only happens on Windows.. Check this shell log, when you get int(123) it didn't happen:

$ php reproduce.php
int(123)
PHP Fatal error:  Uncaught HeadlessChromium\Exception\CommunicationException: Message could not be sent. Reason: the connection is closed. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php:253
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php(269): HeadlessChromium\Communication\Connection->sendMessage('Message could n...')
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Page.php(794): HeadlessChromium\Communication\Connection->sendMessageSync(Object(HeadlessChromium\Communication\Message))
#2 C:\cygwin64\home\hans\reproduce.php(46): HeadlessChromium\Page->close()
#3 [internal function]: Harvest_Chromium->__destruct()
#4 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php on line 253

Fatal error: Uncaught HeadlessChromium\Exception\CommunicationException: Message could not be sent. Reason: the connection is closed. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php:253
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php(269): HeadlessChromium\Communication\Connection->sendMessage('Message could n...')
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Page.php(794): HeadlessChromium\Communication\Connection->sendMessageSync(Object(HeadlessChromium\Communication\Message))
#2 C:\cygwin64\home\hans\reproduce.php(46): HeadlessChromium\Page->close()
#3 [internal function]: Harvest_Chromium->__destruct()
#4 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php on line 253

hans@DESKTOP-EE15SLU ~
$ php reproduce.php
int(123)
PHP Fatal error:  Uncaught HeadlessChromium\Exception\CommunicationException: Message could not be sent. Reason: the connection is closed. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php:253
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php(269): HeadlessChromium\Communication\Connection->sendMessage('Message could n...')
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Page.php(794): HeadlessChromium\Communication\Connection->sendMessageSync(Object(HeadlessChromium\Communication\Message))
#2 C:\cygwin64\home\hans\reproduce.php(46): HeadlessChromium\Page->close()
#3 [internal function]: Harvest_Chromium->__destruct()
#4 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php on line 253

Fatal error: Uncaught HeadlessChromium\Exception\CommunicationException: Message could not be sent. Reason: the connection is closed. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php:253
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php(269): HeadlessChromium\Communication\Connection->sendMessage('Message could n...')
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Page.php(794): HeadlessChromium\Communication\Connection->sendMessageSync(Object(HeadlessChromium\Communication\Message))
#2 C:\cygwin64\home\hans\reproduce.php(46): HeadlessChromium\Page->close()
#3 [internal function]: Harvest_Chromium->__destruct()
#4 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\Connection.php on line 253

hans@DESKTOP-EE15SLU ~
$ php reproduce.php
PHP Fatal error:  Uncaught HeadlessChromium\Exception\OperationTimedOut: Operation timed out after 5s. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php:18
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Utils.php(67): HeadlessChromium\Exception\OperationTimedOut::createFromTimeout(5000000)
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\ResponseReader.php(114): HeadlessChromium\Utils::tryWithTimeout(5000000, Object(Generator))
#2 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(77): HeadlessChromium\Communication\ResponseReader->waitForResponse(5000)
#3 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(108): HeadlessChromium\PageUtils\PageEvaluation->waitForResponse(NULL)
#4 C:\cygwin64\home\hans\reproduce.php(70): HeadlessChromium\PageUtils\PageEvaluation->getReturnValue()
#5 C:\cygwin64\home\hans\reproduce.php(42): Harvest_Chromium->login()
#6 C:\cygwin64\home\hans\reproduce.php(76): Harvest_Chromium->__construct('C:\\Program File...', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#7 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php on line 18

Fatal error: Uncaught HeadlessChromium\Exception\OperationTimedOut: Operation timed out after 5s. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php:18
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Utils.php(67): HeadlessChromium\Exception\OperationTimedOut::createFromTimeout(5000000)
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\ResponseReader.php(114): HeadlessChromium\Utils::tryWithTimeout(5000000, Object(Generator))
#2 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(77): HeadlessChromium\Communication\ResponseReader->waitForResponse(5000)
#3 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(108): HeadlessChromium\PageUtils\PageEvaluation->waitForResponse(NULL)
#4 C:\cygwin64\home\hans\reproduce.php(70): HeadlessChromium\PageUtils\PageEvaluation->getReturnValue()
#5 C:\cygwin64\home\hans\reproduce.php(42): Harvest_Chromium->login()
#6 C:\cygwin64\home\hans\reproduce.php(76): Harvest_Chromium->__construct('C:\\Program File...', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#7 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php on line 18

hans@DESKTOP-EE15SLU ~
$ php reproduce.php
PHP Fatal error:  Uncaught HeadlessChromium\Exception\OperationTimedOut: Operation timed out after 5s. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php:18
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Utils.php(67): HeadlessChromium\Exception\OperationTimedOut::createFromTimeout(5000000)
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\ResponseReader.php(114): HeadlessChromium\Utils::tryWithTimeout(5000000, Object(Generator))
#2 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(77): HeadlessChromium\Communication\ResponseReader->waitForResponse(5000)
#3 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(108): HeadlessChromium\PageUtils\PageEvaluation->waitForResponse(NULL)
#4 C:\cygwin64\home\hans\reproduce.php(70): HeadlessChromium\PageUtils\PageEvaluation->getReturnValue()
#5 C:\cygwin64\home\hans\reproduce.php(42): Harvest_Chromium->login()
#6 C:\cygwin64\home\hans\reproduce.php(76): Harvest_Chromium->__construct('C:\\Program File...', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#7 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php on line 18

Fatal error: Uncaught HeadlessChromium\Exception\OperationTimedOut: Operation timed out after 5s. in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php:18
Stack trace:
#0 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Utils.php(67): HeadlessChromium\Exception\OperationTimedOut::createFromTimeout(5000000)
#1 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Communication\ResponseReader.php(114): HeadlessChromium\Utils::tryWithTimeout(5000000, Object(Generator))
#2 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(77): HeadlessChromium\Communication\ResponseReader->waitForResponse(5000)
#3 C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\PageUtils\PageEvaluation.php(108): HeadlessChromium\PageUtils\PageEvaluation->waitForResponse(NULL)
#4 C:\cygwin64\home\hans\reproduce.php(70): HeadlessChromium\PageUtils\PageEvaluation->getReturnValue()
#5 C:\cygwin64\home\hans\reproduce.php(42): Harvest_Chromium->login()
#6 C:\cygwin64\home\hans\reproduce.php(76): Harvest_Chromium->__construct('C:\\Program File...', Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#7 {main}
  thrown in C:\cygwin64\home\hans\vendor\chrome-php\chrome\src\Exception\OperationTimedOut.php on line 18

it's like a 50% chance of timing out on that "123" eval!

any idea why $page->evaluate('123')->getReturnValue(); would throw like this? but only on Windows?

Can reproduce on: PHP 8.3.0, chrome-php/chrome 1.10.0, Windows 10 x64, Chrome Version 120.0.6099.71 (Official Build) (64-bit)

Can not reproduce on: PHP 8.2.13, chrome-php/chrome 1.10.0, Ubuntu Linux 22.04 x64, Chromium Version 120.0.6099.71 (Official Build) snap (64-bit)

Both are running on a very fast system, definitely should not be a CPU issue (AMD Ryzen 9 7950x, 64GB RAM), should not be a network connection issue either, running on a shared office 1gbps internet connection, with cable.. i'm certain it's not a network issue. .

GrahamCampbell commented 9 months ago

Thanks for the report. I'm not going to be able to look into this issue (I say this to set expectations), but if you'd like to, that's fine.

enricodias commented 9 months ago

I don't have access to a windows machine at this moment, but I'll try to take a look at it in the next week.

divinity76 commented 5 months ago

Still happens sporadically on Windows 11, PHP 8.3.6, chrome-php/chrome 1.11.0, forcing me to make hacks like

-        $loginError = $page->evaluate('document.querySelector("[role=alert]")?.textContent')->getReturnValue();
+        try {
+            $loginError = $page->evaluate('document.querySelector("[role=alert]")?.textContent')->getReturnValue();
+        } catch(\Throwable $ex){
+            sleep(1);
+            $loginError = $page->evaluate('document.querySelector("[role=alert]")?.textContent')->getReturnValue();
+        }

For Windows compatibility:

document.querySelector("[role=alert]")?.textContent

Sometimes timeouts (getReturnValue() crashing on timeout, but only on Windows)

divinity76 commented 5 months ago

Theorizing that it might be a timing issue where chrome-php sends the JavaScript to chrome before setting up the listener for the result, and depending on how the OS thread scheduler work, the result might be sent before the listener for the result is set up? Maybe? (In which case a reordering of setting up listener and sending the JavaScript should fix it)