symfony / panther

A browser testing and web crawling library for PHP and Symfony
MIT License
2.91k stars 213 forks source link

Can we add an option to not start a geckodriver listener? #607

Open halfer opened 8 months ago

halfer commented 8 months ago

I have a change suggestion for Panther that I want to check with you first. I might be able to find the time to raise a PR here, but I want to see if the project is alive, committers are able to review the work, that the change is inline with best practices, etc.

I have a Dockerised app and I want to browser-test it. I decided against putting test systems in the same container, so found an image for Firefox+GeckoDriver (instrumentisto/geckodriver) and connect the two via a Docker Compose network. This container starts a geckodriver listener already, so I want Panther not to do this.

Unfortunately the FirefoxManager does not support this. Here is the relevant code:

    public function start(): WebDriver
    {
        $url = $this->options['scheme'].'://'.$this->options['host'].':'.$this->options['port'];
        if (!$this->process->isRunning()) {
            $this->checkPortAvailable($this->options['host'], $this->options['port']);
            $this->process->start();
            $this->waitUntilReady($this->process, $url.$this->options['path'], 'firefox');
        }

        $firefoxOptions = [];
        if (isset($_SERVER['PANTHER_FIREFOX_BINARY'])) {
            $firefoxOptions['binary'] = $_SERVER['PANTHER_FIREFOX_BINARY'];
        }
        if ($this->arguments) {
            $firefoxOptions['args'] = $this->arguments;
        }

        $capabilities = DesiredCapabilities::firefox();
        $capabilities->setCapability('moz:firefoxOptions', $firefoxOptions);

        foreach ($this->options['capabilities'] as $capability => $value) {
            $capabilities->setCapability($capability, $value);
        }

        return RemoteWebDriver::create($url, $capabilities, $this->options['connection_timeout_in_ms'] ?? null, $this->options['request_timeout_in_ms'] ?? null);
    }

I have passed a fake geckodriver shell to the ctor, and set up a host/port on a geckodriver container, but $this->checkPortAvailable() gets in the way. It requires the port to not be listening, so the listener process can be started, and then the port is checked until it is ready. Since the port is already listening, an error is thrown:

RuntimeException: The port 4444 is already in use.

It looks like this whole block could do with being skipped:

if (!$this->process->isRunning()) {
    $this->checkPortAvailable($this->options['host'], $this->options['port']);
    $this->process->start();
    $this->waitUntilReady($this->process, $url.$this->options['path'], 'firefox');
}

If I could call start() on this object then I assume that this would be skipped, but the Process instantiation is buried in a ctor, so I can't get to it.

I also can't extend the class because it is marked as final.

I have fixed this in the short term by copying FirefoxManager and making some edits to my copy, but I wonder if there is a more elegant solution. Perhaps an option can be passed in to skip the process checks? (Whatever changes are done here would have to be done for ChromeManager too).

I appreciate I could switch to Selenium, but I'd be unpicking a fair bit of work, and the existence of a separate geckodriver container suggests to me that some folks are using already-started FF listeners in their test stack.