From 615d1b8d16c0aa51605776d7e02ce7f022b3f9aa Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Mon, 4 Nov 2024 21:03:17 +1100 Subject: [PATCH] Removed Docker from the dev stack. --- .github/workflows/test.yml | 82 ++++++++++--------- README.md | 26 +++--- behat.yml | 37 +++++---- composer.json | 7 +- docker-compose.yml | 10 --- .../Context/ScreenshotContext.php | 22 +++-- .../BehatScreenshotExtension.php | 37 ++++++--- tests/behat/bootstrap/BehatCliTrait.php | 22 +++-- tests/behat/bootstrap/FeatureContext.php | 32 ++++++++ tests/behat/features/behatcli.feature | 10 +-- tests/behat/features/selenium.feature | 62 +++++++------- 11 files changed, 195 insertions(+), 152 deletions(-) delete mode 100644 docker-compose.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ad9f15..57d816b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,59 +20,61 @@ jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['8.2', '8.3'] + + services: + chrome: + image: selenium/standalone-chromium:130.0 + ports: + - 4444:4444 + steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: /tmp/composer-cache + key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} - - name: Set up Docker Compose - run: docker compose up -d --build + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + ini-values: pcov.directory=. - - name: Copy codebase into container - run: | - container_id="$(docker compose ps -q phpserver)" - docker cp -L . "${container_id}":/app/ + - name: Install dependencies + run: composer install - - name: Install Composer dependencies - run: docker compose exec phpserver composer install --ansi --no-suggest + - name: Validate composer.json + run: | + composer --verbose validate + composer normalize --dry-run - - name: Lint code - run: docker compose exec phpserver composer lint + - name: Check coding standards + run: composer lint continue-on-error: ${{ vars.CI_LINT_IGNORE_FAILURE == '1' }} - - name: Restart server with Xdebug enabled - run: XDEBUG_ENABLE=true docker compose up -d phpserver - - - name: Run tests with PHPUnit - run: docker compose exec -T -e XDEBUG_MODE=coverage phpserver vendor/bin/phpunit - continue-on-error: ${{ vars.CI_TEST_IGNORE_FAILURE == '1' }} + - name: Run unit tests + run: composer test-unit + continue-on-error: ${{ vars.CI_TEST_UNIT_IGNORE_FAILURE == '1' }} - - name: Run tests with Behat - run: docker compose exec -T -e XDEBUG_MODE=coverage phpserver vendor/bin/behat - continue-on-error: ${{ vars.CI_TEST_IGNORE_FAILURE == '1' }} - - - name: Process test logs and artifacts - run: | - mkdir -p /tmp/logs - if docker compose ps --services --filter "status=running" | grep -q phpserver && docker compose exec phpserver test -d /app/.logs; then - docker compose cp phpserver:/app/.logs/. "/tmp/logs/" - fi - - - name: Upload artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: artifacts - path: /tmp/logs + - name: Run BDD tests + run: composer test-bdd + continue-on-error: ${{ vars.CI_TEST_BDD_IGNORE_FAILURE == '1' }} + env: + BEHAT_JAVASCRIPT_BASE_URL: http://172.17.0.1:8888 - name: Upload logs as artifacts if: always() uses: actions/upload-artifact@v4 with: - name: logs - path: /tmp/logs + name: ${{github.job}}-logs-${{ matrix.php-versions }} + path: .logs include-hidden-files: true if-no-files-found: error @@ -81,8 +83,8 @@ jobs: if: ${{ env.CODECOV_TOKEN != '' }} with: files: | - /tmp/logs/coverage/behat/cobertura.xml - /tmp/logs/coverage/phpunit/cobertura.xml + .logs/coverage/behat/cobertura.xml + .logs/coverage/phpunit/cobertura.xml fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} env: @@ -91,4 +93,4 @@ jobs: - name: Setup tmate session if: ${{ !cancelled() && github.event.inputs.enable_terminal }} uses: mxschmitt/action-tmate@v3 - timeout-minutes: 5 + timeout-minutes: 15 diff --git a/README.md b/README.md index 6f8723e..f9833bc 100644 --- a/README.md +++ b/README.md @@ -128,39 +128,35 @@ You may optionally specify size of browser window in the screenshot step: ### Local development setup -```bash -docker compose up -d -docker compose exec phpserver composer install --ansi -``` - -### Lint code +### Install dependencies. ```bash -docker compose exec phpserver composer lint +composer install ``` -### Lint fix +### Start Chrome container. ```bash -docker compose exec phpserver composer lint-fix +docker run -d -p 4444:4444 selenium/standalone-chromium:130.0 ``` -### Run tests +### Lint code ```bash -docker compose exec phpserver composer test +composer lint ``` -### Enable Xdebug +### Fix code style ```bash -XDEBUG_ENABLE=true docker compose up -d phpserver +composer lint-fix ``` -To disable, run +### Run tests ```bash -docker compose up -d phpserver +composer test-unit # Run unit tests. +composer test-bdd # Run BDD tests. ``` --- diff --git a/behat.yml b/behat.yml index 1f23ca8..388560a 100644 --- a/behat.yml +++ b/behat.yml @@ -1,35 +1,27 @@ default: - autoload: [ "%paths.base%/tests/behat/bootstrap" ] + autoload: ["%paths.base%/tests/behat/bootstrap"] suites: default: - paths: [ "%paths.base%/tests/behat/features" ] + paths: ["%paths.base%/tests/behat/features"] contexts: + - FeatureContext: + - screenshot_dir: '%paths.base%/.logs/screenshots' - BehatCliContext - - DrevOps\BehatPhpServer\PhpServerContext: - - - docroot: "%paths.base%/tests/behat/features/fixtures" - host: "phpserver" - DrevOps\BehatScreenshotExtension\Context\ScreenshotContext - - FeatureContext: - - - screenshot_dir: '%paths.base%/.logs/screenshots' - - formatters: - pretty: true - # Disable JUnit formatter if memory leaks start to occur. - # See @https://github.com/Behat/Behat/pull/1423 - junit: - output_path: '%paths.base%/.logs/test_results/behat' + - DrevOps\BehatPhpServer\PhpServerContext: + - docroot: "%paths.base%/tests/behat/features/fixtures" + host: "0.0.0.0" + port: 8888 extensions: Behat\MinkExtension: browserkit_http: ~ files_path: "%paths.base%/tests/behat/features/fixtures" + base_url: http://0.0.0.0:8888 browser_name: chrome - base_url: http://phpserver:8888 javascript_session: selenium2 selenium2: - wd_host: "http://chrome:4444/wd/hub" + wd_host: "http://localhost:4444/wd/hub" capabilities: browser: chrome extra_capabilities: @@ -43,10 +35,12 @@ default: - '--disable-translate' # Disables the built-in translation feature, preventing Chrome from offering to translate pages. - '--no-first-run' # Skips the initial setup screen that Chrome typically shows when running for the first time. - '--test-type' # Disables certain security features and UI components that are unnecessary for automated testing, making Chrome more suitable for test environments. + DrevOps\BehatScreenshotExtension: dir: '%paths.base%/.logs/screenshots' fail: true purge: true + DVDoug\Behat\CodeCoverage\Extension: filter: include: @@ -60,3 +54,10 @@ default: target: .logs/coverage/behat/.coverage-html cobertura: target: .logs/coverage/behat/cobertura.xml + + formatters: + pretty: true + # Disable JUnit formatter if memory leaks start to occur. + # See @https://github.com/Behat/Behat/pull/1423 + junit: + output_path: '%paths.base%/.logs/test_results/behat' diff --git a/composer.json b/composer.json index 816c79f..31e15f2 100644 --- a/composer.json +++ b/composer.json @@ -66,9 +66,8 @@ "rector --clear-cache", "phpcbf" ], - "test": [ - "if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi", - "behat" - ] + "reset": "rm -Rf vendor vendor-bin composer.lock", + "test-bdd": "behat --colors", + "test-unit": "phpunit" } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 922b8b6..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - phpserver: - image: uselagoon/php-8.2-cli:24.10.0 - volumes: - - .:/app:delegated - environment: - # To enable xdebug: XDEBUG_ENABLE=true docker compose up -d phpserver - XDEBUG_ENABLE: ${XDEBUG_ENABLE:-} - chrome: - image: selenium/standalone-chromium:130.0 diff --git a/src/DrevOps/BehatScreenshotExtension/Context/ScreenshotContext.php b/src/DrevOps/BehatScreenshotExtension/Context/ScreenshotContext.php index b7ae778..7cb698c 100644 --- a/src/DrevOps/BehatScreenshotExtension/Context/ScreenshotContext.php +++ b/src/DrevOps/BehatScreenshotExtension/Context/ScreenshotContext.php @@ -18,6 +18,7 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class ScreenshotContext extends RawMinkContext implements ScreenshotAwareContextInterface { + /** * Screenshot step line. */ @@ -87,13 +88,13 @@ public function beforeScenarioInit(BeforeScenarioScope $scope): void { } catch (\Exception $exception) { throw new \RuntimeException( - sprintf( - 'Please make sure that Selenium server is running. %s', - $exception->getMessage(), - ), - $exception->getCode(), - $exception, - ); + sprintf( + 'Please make sure that Selenium server is running. %s', + $exception->getMessage(), + ), + $exception->getCode(), + $exception, + ); } } } @@ -299,6 +300,13 @@ protected function makeFileName(string $ext, ?string $filename = NULL, bool $fai $url = NULL; } + if (!empty($url) && !empty(getenv('BEHAT_SCREENSHOT_TOKEN_HOST'))) { + $host = parse_url($url, PHP_URL_HOST); + if ($host) { + $url = str_replace($host, getenv('BEHAT_SCREENSHOT_TOKEN_HOST'), $url); + } + } + $data = [ 'ext' => $ext, 'step_name' => $step->getText(), diff --git a/src/DrevOps/BehatScreenshotExtension/ServiceContainer/BehatScreenshotExtension.php b/src/DrevOps/BehatScreenshotExtension/ServiceContainer/BehatScreenshotExtension.php index f2ea121..7db25eb 100644 --- a/src/DrevOps/BehatScreenshotExtension/ServiceContainer/BehatScreenshotExtension.php +++ b/src/DrevOps/BehatScreenshotExtension/ServiceContainer/BehatScreenshotExtension.php @@ -43,19 +43,36 @@ public function initialize(ExtensionManager $extensionManager): void { * {@inheritdoc} */ public function configure(ArrayNodeDefinition $builder): void { - $definitionChildren = $builder->children(); + // @phpcs:disable Drupal.WhiteSpace.ObjectOperatorIndent.Indent + // @formatter:off // @phpstan-ignore-next-line - $definitionChildren - ->scalarNode('dir')->cannotBeEmpty()->defaultValue('%paths.base%/screenshots')->end() - ->scalarNode('fail')->cannotBeEmpty()->defaultValue(TRUE)->end() - ->scalarNode('fail_prefix')->cannotBeEmpty()->defaultValue('failed_')->end() - ->scalarNode('purge')->cannotBeEmpty()->defaultValue(FALSE)->end() + $builder->children() + ->scalarNode('dir') + ->cannotBeEmpty() + ->defaultValue('%paths.base%/screenshots') + ->end() + ->scalarNode('fail') + ->cannotBeEmpty() + ->defaultValue(TRUE) + ->end() + ->scalarNode('fail_prefix') + ->cannotBeEmpty() + ->defaultValue('failed_') + ->end() + ->scalarNode('purge') + ->cannotBeEmpty() + ->defaultValue(FALSE) + ->end() ->scalarNode('filenamePattern') - ->cannotBeEmpty() - ->defaultValue('{datetime:U}.{feature_file}.feature_{step_line}.{ext}')->end() + ->cannotBeEmpty() + ->defaultValue('{datetime:U}.{feature_file}.feature_{step_line}.{ext}') + ->end() ->scalarNode('filenamePatternFailed') - ->cannotBeEmpty() - ->defaultValue('{datetime:U}.{fail_prefix}{feature_file}.feature_{step_line}.{ext}')->end(); + ->cannotBeEmpty() + ->defaultValue('{datetime:U}.{fail_prefix}{feature_file}.feature_{step_line}.{ext}') + ->end(); + // @formatter:on + // @phpcs:enable Drupal.WhiteSpace.ObjectOperatorIndent.Indent } /** diff --git a/tests/behat/bootstrap/BehatCliTrait.php b/tests/behat/bootstrap/BehatCliTrait.php index 1f3277a..ad1ecc2 100644 --- a/tests/behat/bootstrap/BehatCliTrait.php +++ b/tests/behat/bootstrap/BehatCliTrait.php @@ -98,7 +98,7 @@ public function __construct($parameters) */ public function goToPhpServerTestPage() { - $this->getSession()->visit('http://phpserver:8888/screenshot.html'); + $this->getSession()->visit('http://0.0.0.0:8888/screenshot.html'); } /** @@ -171,12 +171,11 @@ public function behatCliWriteBehatYml() { default: contexts: - FeatureContextTest: - - - screenshot_dir: "%paths.base%/screenshots" + - screenshot_dir: "%paths.base%/screenshots" - DrevOps\BehatPhpServer\PhpServerContext: - - - docroot: "%paths.base%/tests/behat/features/fixtures" - host: "phpserver" + - docroot: "%paths.base%/tests/behat/features/fixtures" + host: "0.0.0.0" + port: 8888 extensions: Behat\MinkExtension: browserkit_http: ~ @@ -202,18 +201,17 @@ public function behatCliWriteScreenshotContextBehatYml(PyStringNode $value) { default: contexts: - FeatureContextTest: - - - screenshot_dir: "%paths.base%/screenshots" + - screenshot_dir: "%paths.base%/screenshots" - DrevOps\BehatPhpServer\PhpServerContext: - - - docroot: "%paths.base%/tests/behat/features/fixtures" - host: "phpserver" + - docroot: "%paths.base%/tests/behat/features/fixtures" + host: "0.0.0.0" + port: 8888 - DrevOps\BehatScreenshotExtension\Context\ScreenshotContext extensions: Behat\MinkExtension: browserkit_http: ~ selenium2: ~ - base_url: http://nginx:8080 + base_url: http://0.0.0.0:8888 EOL; $content .= PHP_EOL . ' ' . trim((string) $value); diff --git a/tests/behat/bootstrap/FeatureContext.php b/tests/behat/bootstrap/FeatureContext.php index a40a1cb..0650fea 100644 --- a/tests/behat/bootstrap/FeatureContext.php +++ b/tests/behat/bootstrap/FeatureContext.php @@ -6,13 +6,23 @@ */ use Behat\Behat\Context\Context; +use Behat\Behat\Context\Environment\InitializedContextEnvironment; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\MinkExtension\Context\MinkContext; +use Behat\MinkExtension\Context\RawMinkContext; /** * Defines application features from the specific context. */ class FeatureContext extends MinkContext implements Context { + /** + * Base URL for JavaScript scenarios. + * + * @var string + */ + protected string $javascriptBaseUrl; + use ScreenshotTrait; /** @@ -23,6 +33,28 @@ class FeatureContext extends MinkContext implements Context { */ public function __construct(array $parameters) { $this->screenshotInitParams($parameters); + // Set the screenshot token host to override any real host. + putenv('BEHAT_SCREENSHOT_TOKEN_HOST=example.com'); + // Set the JavaScript override base URL. + $this->javascriptBaseUrl = getenv('BEHAT_JAVASCRIPT_BASE_URL') ?: 'http://host.docker.internal:8888'; + } + + /** + * Update base URL for JavaScript scenarios. + * + * @BeforeScenario + */ + public function beforeScenarioUpdateBaseUrl(BeforeScenarioScope $scope): void { + if ($scope->getScenario()->hasTag('javascript')) { + $environment = $scope->getEnvironment(); + if ($environment instanceof InitializedContextEnvironment) { + foreach ($environment->getContexts() as $context) { + if ($context instanceof RawMinkContext) { + $context->setMinkParameter('base_url', $this->javascriptBaseUrl); + } + } + } + } } } diff --git a/tests/behat/features/behatcli.feature b/tests/behat/features/behatcli.feature index 9e0f02e..7fdb7a9 100644 --- a/tests/behat/features/behatcli.feature +++ b/tests/behat/features/behatcli.feature @@ -22,7 +22,7 @@ Feature: Behat CLI context */ public function goToPhpServerTestPage() { - $this->getSession()->visit('http://phpserver:8888/screenshot.html'); + $this->getSession()->visit('http://0.0.0.0:8888/screenshot.html'); } /** @@ -41,14 +41,14 @@ Feature: Behat CLI context contexts: - FeatureContextTest - DrevOps\BehatPhpServer\PhpServerContext: - - - docroot: "%paths.base%/tests/behat/features/fixtures" - host: "phpserver" + - docroot: "%paths.base%/tests/behat/features/fixtures" + host: "0.0.0.0" + port: 8888 extensions: Behat\MinkExtension: browserkit_http: ~ selenium2: ~ - base_url: http://nginx:8080 + base_url: http://0.0.0.0:8888 """ And a file named "tests/behat/features/fixtures/screenshot.html" with: """ diff --git a/tests/behat/features/selenium.feature b/tests/behat/features/selenium.feature index 25af340..47206ce 100644 --- a/tests/behat/features/selenium.feature +++ b/tests/behat/features/selenium.feature @@ -4,67 +4,67 @@ Feature: Selenium screenshots @phpserver @javascript Scenario: Capture a screenshot using Selenium driver - When I am on the screenshot test page - And save screenshot + Given I am on the screenshot test page + When save screenshot Then file wildcard "*.selenium.feature_8.png" should exist And file wildcard "*.selenium.feature_8.html" should exist - And save 800 x 600 screenshot + When save 800 x 600 screenshot Then file wildcard "*.selenium.feature_11.png" should exist - And save 1440 x 900 screenshot + When save 1440 x 900 screenshot And file wildcard "*.selenium.feature_13.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page - And I save screenshot with name "hello-selenium-screenshot" + Given I am on the screenshot test page + When I save screenshot with name "hello-selenium-screenshot" Then file wildcard "hello-selenium-screenshot.png" should exist And file wildcard "hello-selenium-screenshot.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page - And I save screenshot with name "test.{url_domain}.{url_path}.{ext}" - Then file wildcard "*.phpserver.screenshot.html.png" should exist - Then file wildcard "*.phpserver.screenshot.html.html" should exist + Given I am on the screenshot test page + When I save screenshot with name "test.{url_domain}.{url_path}.{ext}" + Then file wildcard "*.example\.com.screenshot\.html\.png" should exist + And file wildcard "*.example\.com.screenshot\.html\.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page - And I save screenshot with name "test.{url_origin}.{ext}" - Then file wildcard "*.http%3A%2F%2Fphpserver.png" should exist - Then file wildcard "*.http%3A%2F%2Fphpserver.html" should exist + Given I am on the screenshot test page + When I save screenshot with name "test.{url_origin}.{ext}" + Then file wildcard "*.http%3A%2F%2Fexample\.com\.png" should exist + And file wildcard "*.http%3A%2F%2Fexample\.com\.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page - And I save screenshot with name "test.{url}.{ext}" - Then file wildcard "*.http%3A%2F%2Fphpserver%3A8888%2Fscreenshot.html.png" should exist - Then file wildcard "*.http%3A%2F%2Fphpserver%3A8888%2Fscreenshot.html.html" should exist + Given I am on the screenshot test page + When I save screenshot with name "test.{url}.{ext}" + Then file wildcard "*.http%3A%2F%2Fexample\.com%3A8888%2Fscreenshot.html.png" should exist + And file wildcard "*.http%3A%2F%2Fexample\.com%3A8888%2Fscreenshot.html.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" - And I save screenshot with name "test.{url_query}.{url_fragment}.{ext}" + Given I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" + When I save screenshot with name "test.{url_query}.{url_fragment}.{ext}" Then file wildcard "*.foo%3Dtest-foo.foo-fragment.png" should exist - Then file wildcard "*.foo%3Dtest-foo.foo-fragment.html" should exist + And file wildcard "*.foo%3Dtest-foo.foo-fragment.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" - And I save screenshot with name "test.{url_relative}.{ext}" + Given I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" + When I save screenshot with name "test.{url_relative}.{ext}" Then file wildcard "*.screenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.png" should exist - Then file wildcard "*.screenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.html" should exist + And file wildcard "*.screenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" - And I save screenshot with name "test.{url}.{ext}" - Then file wildcard "*.http%3A%2F%2Fphpserver%3A8888%2Fscreenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.png" should exist - Then file wildcard "*.http%3A%2F%2Fphpserver%3A8888%2Fscreenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.html" should exist + Given I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" + When I save screenshot with name "test.{url}.{ext}" + Then file wildcard "*.http%3A%2F%2Fexample\.com%3A8888%2Fscreenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.png" should exist + And file wildcard "*.http%3A%2F%2Fexample\.com%3A8888%2Fscreenshot.html%3Ffoo%3Dtest-foo%23foo-fragment.html" should exist @phpserver @javascript Scenario: Capture a screenshot with name using Selenium driver - When I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" - And I save screenshot with name "test.{step_line}.{step_name}.{ext}" + Given I am on the screenshot test page with query "foo=test-foo" and fragment "foo-fragment" + When I save screenshot with name "test.{step_line}.{step_name}.{ext}" Then file wildcard "*.I_save_screenshot_with_name_test.{step_line}.{step_name}.{ext}.png" should exist - Then file wildcard "*.I_save_screenshot_with_name_test.{step_line}.{step_name}.{ext}.html" should exist + And file wildcard "*.I_save_screenshot_with_name_test.{step_line}.{step_name}.{ext}.html" should exist