diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa3f9e7..efb0680 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,19 +9,19 @@ jobs: tests: runs-on: ubuntu-latest continue-on-error: false - name: "PHP ${{ matrix.php }}" + name: "Behat 3 (PHP ${{ matrix.php }})" strategy: fail-fast: false matrix: php: - - '7.4' - - '8.0' - - '8.1' + - '8.2' + - '8.3' + - '8.4' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -32,14 +32,17 @@ jobs: tools: composer:v2 - name: Run Chrome Headless - run: google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1 > /dev/null 2>&1 & + run: google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless=new --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1 > "${{ runner.temp }}/chrome.log" 2>&1 & + + - name: Run web server + run: php -S 127.0.0.1:8080 -t test-application/public > "${{ runner.temp }}/server.log" 2>&1 & - name: Get Composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache Composer - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json **/composer.lock') }} @@ -52,5 +55,82 @@ jobs: - name: Validate composer.json run: composer validate --ansi --strict + - name: Wait for Chrome DevTools and web server + run: | + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:9222/json/version > /dev/null && curl -sf http://127.0.0.1:8080 > /dev/null; then + echo "Chrome DevTools and web server are up"; exit 0 + fi + sleep 1 + done + echo "Chrome DevTools or web server did not become ready" + cat "${{ runner.temp }}/chrome.log" || true + cat "${{ runner.temp }}/server.log" || true + exit 1 + + - name: Run Behat + run: vendor/bin/behat --colors --strict --no-interaction -vvv -f progress + + tests-behat4: + runs-on: ubuntu-latest + continue-on-error: false + name: "Behat 4 (PHP ${{ matrix.php }})" + + # friends-of-behat/mink-extension v3 (Behat 4) requires PHP ^8.3, so this leg runs on 8.3+. + # Behat 4 and mink-extension v3 are still in alpha, so the alpha line is opted into here (via the + # explicit @alpha require) rather than in composer.json, keeping the released constraints alias-free. + strategy: + fail-fast: false + matrix: + php: + - '8.3' + - '8.4' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + coverage: none + ini-values: "memory_limit=-1" + php-version: ${{ matrix.php }} + tools: composer:v2 + + - name: Run Chrome Headless + run: google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless=new --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1 > "${{ runner.temp }}/chrome.log" 2>&1 & + + - name: Run web server + run: php -S 127.0.0.1:8080 -t test-application/public > "${{ runner.temp }}/server.log" 2>&1 & + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php }}-composer-behat4-${{ hashFiles('**/composer.json **/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php }}-composer-behat4- + + - name: Install PHP dependencies (Behat 4) + run: composer require behat/behat:"^4.0@alpha" friends-of-behat/mink-extension:"^3.0@alpha" --with-all-dependencies --no-interaction + + - name: Wait for Chrome DevTools and web server + run: | + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:9222/json/version > /dev/null && curl -sf http://127.0.0.1:8080 > /dev/null; then + echo "Chrome DevTools and web server are up"; exit 0 + fi + sleep 1 + done + echo "Chrome DevTools or web server did not become ready" + cat "${{ runner.temp }}/chrome.log" || true + cat "${{ runner.temp }}/server.log" || true + exit 1 + - name: Run Behat run: vendor/bin/behat --colors --strict --no-interaction -vvv -f progress diff --git a/composer.json b/composer.json index 1f705da..cb8b1aa 100644 --- a/composer.json +++ b/composer.json @@ -18,16 +18,18 @@ } ], "require": { - "php": ">=7.4", - "behat/behat": "^3.5", - "behat/mink-extension": "^2.3" + "php": ">=8.2", + "behat/behat": "^3.5 || ^4.0", + "friends-of-behat/mink-extension": "^2.3 || ^3.0" }, "require-dev": { - "behat/mink-goutte-driver": "^1.2", - "behat/mink-selenium2-driver": "^1.4", - "dmore/behat-chrome-extension": "^1.3", - "dmore/chrome-mink-driver": "^2.7", - "symfony/process": "^4.4 || ^5.2" + "behat/mink-browserkit-driver": "^2.3", + "behat/mink-selenium2-driver": "^1.7", + "dmore/chrome-mink-driver": "^2.9", + "sylius-labs/behat-chrome-extension": "^1.5", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/http-client": "^6.4 || ^7.4 || ^8.0", + "symfony/process": "^6.4 || ^7.4 || ^8.0" }, "extra": { "branch-alias": { diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index bdfde89..e71736f 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -4,6 +4,10 @@ use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; +use Behat\Hook\BeforeScenario; +use Behat\Step\Given; +use Behat\Step\Then; +use Behat\Step\When; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; @@ -18,9 +22,7 @@ final class FeatureContext implements Context /** @var string */ private string $testApplicationDir; - /** - * @BeforeScenario - */ + #[BeforeScenario] public function prepareProcess(): void { $phpFinder = new PhpExecutableFinder(); @@ -32,9 +34,7 @@ public function prepareProcess(): void $this->testApplicationDir = __DIR__ . '/../../test-application'; } - /** - * @Given there is following Behat extension configuration: - */ + #[Given('there is following Behat extension configuration:')] public function thereIsBehatExtensionConfiguration(TableNode $table): void { foreach ($table->getRowsHash() as $key => $value) { @@ -42,17 +42,13 @@ public function thereIsBehatExtensionConfiguration(TableNode $table): void } } - /** - * @Given /configuration option "([^"]+?)" is set to "([^"]+?)"/ - */ + #[Given('/configuration option "([^"]+?)" is set to "([^"]+?)"/')] public function configurationOptionSet(string $key, string $value): void { $this->configuration['%' . $key . '%'] = $value; } - /** - * @When /I run Behat with failing scenarios(?: using (.+?) profile)?/ - */ + #[When('/I run Behat with failing scenarios(?: using (.+?) profile)?/')] public function iRunBehat(?string $profile = null): void { $this->createBehatConfigurationFile(); @@ -62,9 +58,7 @@ public function iRunBehat(?string $profile = null): void $this->deleteBehatConfigurationFile(); } - /** - * @Then there should be text log generated - */ + #[Then('there should be text log generated')] public function thereShouldBeTextLogGenerated(): void { $logPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.html'; @@ -75,9 +69,7 @@ public function thereShouldBeTextLogGenerated(): void } } - /** - * @Then a screenshot should be made - */ + #[Then('a screenshot should be made')] public function screenshotShouldBeMade(): void { $screenshotPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.png'; @@ -88,9 +80,7 @@ public function screenshotShouldBeMade(): void } } - /** - * @Then a screenshot should not be made - */ + #[Then('a screenshot should not be made')] public function screenshotShouldNotBeMade(): void { $screenshotPattern = $this->testApplicationDir . '/' . $this->configuration['%directory%'] . '/*.png'; @@ -103,12 +93,14 @@ public function screenshotShouldNotBeMade(): void private function createBehatConfigurationFile(): void { + $configurationFile = $this->getConfigurationFileName(); + $behatConfiguration = strtr( - file_get_contents($this->testApplicationDir . '/behat.yml.dist'), + file_get_contents($this->testApplicationDir . '/' . $configurationFile . '.dist'), $this->configuration ); - file_put_contents($this->testApplicationDir . '/behat.yml', $behatConfiguration); + file_put_contents($this->testApplicationDir . '/' . $configurationFile, $behatConfiguration); } private function getExtraConfiguration(?string $profile): array @@ -136,8 +128,20 @@ private function doRunBehat(array $extraConfiguration): void private function deleteBehatConfigurationFile(): void { - if (file_exists($behatFile = $this->testApplicationDir . '/behat.yml')) { + if (file_exists($behatFile = $this->testApplicationDir . '/' . $this->getConfigurationFileName())) { unlink($behatFile); } } + + /** + * Behat 4 dropped YAML configuration support and only auto-discovers PHP configuration files, + * so the templated config file has to match the installed Behat version. The `Behat\Config` + * builder exists in Behat 3.x too, so the major version is what decides here. + */ + private function getConfigurationFileName(): string + { + $behatVersion = (int) \Composer\InstalledVersions::getVersion('behat/behat'); + + return $behatVersion >= 4 ? 'behat.php' : 'behat.yml'; + } } diff --git a/test-application/behat.php.dist b/test-application/behat.php.dist new file mode 100644 index 0000000..4a768ea --- /dev/null +++ b/test-application/behat.php.dist @@ -0,0 +1,64 @@ +withProfile( + (new Profile('default')) + ->withSuite( + (new Suite('default')) + ->withContexts(MinkContext::class) + ) + ->withExtension(new Extension(MinkDebugExtension::class, [ + 'directory' => '%directory%', + 'clean_start' => %clean_start%, + ])) + ->withExtension(new Extension(MinkExtension::class, [ + 'base_url' => 'http://127.0.0.1:8080', + 'sessions' => [ + 'default' => [ + 'browserkit_http' => null, + ], + ], + ])) + ->withGherkinOptions( + (new GherkinOptions())->withFilter(new TagFilter('~@javascript')) + ) + ) + ->withProfile( + (new Profile('javascript')) + ->withExtension(new Extension(MinkDebugExtension::class, [ + 'directory' => '%directory%', + 'screenshot' => %screenshot%, + 'clean_start' => %clean_start%, + ])) + ->withExtension(new Extension(ChromeExtension::class, [])) + ->withExtension(new Extension(MinkExtension::class, [ + 'base_url' => 'http://127.0.0.1:8080', + 'javascript_session' => 'chrome', + 'sessions' => [ + 'chrome' => [ + 'chrome' => [ + 'api_url' => 'http://127.0.0.1:9222', + 'validate_certificate' => false, + ], + ], + ], + 'show_auto' => false, + ])) + ->withGherkinOptions( + (new GherkinOptions())->withFilter(new TagFilter('@javascript')) + ) + ) +; diff --git a/test-application/behat.yml.dist b/test-application/behat.yml.dist index 226e5ff..05c137e 100755 --- a/test-application/behat.yml.dist +++ b/test-application/behat.yml.dist @@ -10,9 +10,10 @@ default: clean_start: %clean_start% Behat\MinkExtension: + base_url: 'http://127.0.0.1:8080' sessions: default: - goutte: ~ + browserkit_http: ~ gherkin: filters: @@ -28,6 +29,7 @@ javascript: DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~ Behat\MinkExtension: + base_url: 'http://127.0.0.1:8080' javascript_session: chrome sessions: chrome: diff --git a/test-application/features/test.feature b/test-application/features/test.feature index c2b400b..e8e6296 100755 --- a/test-application/features/test.feature +++ b/test-application/features/test.feature @@ -4,10 +4,10 @@ Feature: Testing MinkDebugExtension I want to download a page and fail Scenario: Downloading a page and failing - When I go to "https://sylius.com" + When I go to "/" Then I select "Create failing test" from "Available steps" @javascript Scenario: Downloading a page and failing (Javascript session) - When I go to "https://sylius.com" + When I go to "/" Then I select "Create failing test" from "Available steps" diff --git a/test-application/public/index.html b/test-application/public/index.html new file mode 100644 index 0000000..0b52f17 --- /dev/null +++ b/test-application/public/index.html @@ -0,0 +1,11 @@ + + +
+ +This page is served locally so the test suite does not depend on any external website.
+ +