Files
gh-netresearch-claude-code-…/skills/typo3-testing/references/acceptance-testing.md
2025-11-30 08:43:13 +08:00

9.2 KiB

Acceptance Testing in TYPO3

Acceptance tests verify complete user workflows through browser automation using Codeception and Selenium.

When to Use Acceptance Tests

  • Testing complete user journeys (login → browse → checkout)
  • Frontend functionality validation
  • Cross-browser compatibility
  • JavaScript-heavy interactions
  • Visual regression testing

Requirements

  • Docker and Docker Compose
  • Codeception
  • Selenium (ChromeDriver or Firefox)
  • Web server (Nginx/Apache)

Setup

1. Install Codeception

composer require --dev codeception/codeception codeception/module-webdriver codeception/module-asserts

2. Initialize Codeception

vendor/bin/codecept bootstrap

3. Docker Compose

Create Build/docker-compose.yml:

version: '3.8'

services:
  web:
    image: php:8.2-apache
    volumes:
      - ../../:/var/www/html
    ports:
      - "8000:80"
    environment:
      - TYPO3_CONTEXT=Testing
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: typo3_test
    ports:
      - "3306:3306"

  selenium:
    image: selenium/standalone-chrome:latest
    ports:
      - "4444:4444"
    shm_size: 2gb

4. Codeception Configuration

Create codeception.yml:

paths:
    tests: Tests/Acceptance
    output: var/log/acceptance
    data: Tests/Acceptance/_data
    support: Tests/Acceptance/_support
    envs: Tests/Acceptance/_envs

actor_suffix: Tester

extensions:
    enabled:
        - Codeception\Extension\RunFailed

params:
    - .env.testing

suites:
    acceptance:
        actor: AcceptanceTester
        modules:
            enabled:
                - WebDriver:
                    url: http://web:8000
                    browser: chrome
                    host: selenium
                    port: 4444
                - \\Helper\\Acceptance

Test Structure

Basic Test (Cest)

<?php

declare(strict_types=1);

namespace Vendor\Extension\Tests\Acceptance;

use Vendor\Extension\Tests\Acceptance\AcceptanceTester;

final class LoginCest
{
    public function _before(AcceptanceTester $I): void
    {
        // Runs before each test
        $I->amOnPage('/');
    }

    public function loginAsBackendUser(AcceptanceTester $I): void
    {
        $I->amOnPage('/typo3');
        $I->fillField('username', 'admin');
        $I->fillField('password', 'password');
        $I->click('Login');

        $I->see('Dashboard');
        $I->seeInCurrentUrl('/typo3/module/dashboard');
    }

    public function loginFailsWithWrongPassword(AcceptanceTester $I): void
    {
        $I->amOnPage('/typo3');
        $I->fillField('username', 'admin');
        $I->fillField('password', 'wrong_password');
        $I->click('Login');

        $I->see('Login error');
        $I->seeInCurrentUrl('/typo3');
    }
}

Page Objects Pattern

Create reusable page objects:

<?php

declare(strict_types=1);

namespace Vendor\Extension\Tests\Acceptance\PageObject;

use Vendor\Extension\Tests\Acceptance\AcceptanceTester;

final class LoginPage
{
    public static string $URL = '/typo3';

    public static string $usernameField = '#username';
    public static string $passwordField = '#password';
    public static string $loginButton = 'button[type="submit"]';

    private AcceptanceTester $tester;

    public function __construct(AcceptanceTester $I)
    {
        $this->tester = $I;
    }

    public function login(string $username, string $password): void
    {
        $I = $this->tester;

        $I->amOnPage(self::$URL);
        $I->fillField(self::$usernameField, $username);
        $I->fillField(self::$passwordField, $password);
        $I->click(self::$loginButton);
    }
}

Use page object in test:

public function loginWithPageObject(AcceptanceTester $I): void
{
    $loginPage = new LoginPage($I);
    $loginPage->login('admin', 'password');

    $I->see('Dashboard');
}

Common Actions

Navigation

// Navigate to URL
$I->amOnPage('/products');
$I->amOnUrl('https://example.com/page');

// Click links
$I->click('Products');
$I->click('#menu-products');
$I->click(['link' => 'View Details']);

Form Interaction

// Fill fields
$I->fillField('email', 'user@example.com');
$I->fillField('#password', 'secret');

// Select options
$I->selectOption('country', 'Germany');
$I->selectOption('category', ['Books', 'Electronics']);

// Checkboxes and radio buttons
$I->checkOption('terms');
$I->uncheckOption('newsletter');

// Submit forms
$I->submitForm('#contact-form', [
    'name' => 'John Doe',
    'email' => 'john@example.com',
]);

Assertions

// See text
$I->see('Welcome');
$I->see('Product added', '.success-message');
$I->dontSee('Error');

// See elements
$I->seeElement('.product-list');
$I->seeElement('#add-to-cart');
$I->dontSeeElement('.error-message');

// URL checks
$I->seeInCurrentUrl('/checkout');
$I->seeCurrentUrlEquals('/thank-you');

// Field values
$I->seeInField('email', 'user@example.com');

// Number of elements
$I->seeNumberOfElements('.product-item', 10);

JavaScript

// Execute JavaScript
$I->executeJS('window.scrollTo(0, document.body.scrollHeight);');

// Wait for JavaScript
$I->waitForJS('return document.readyState === "complete"', 5);

// Wait for element
$I->waitForElement('.product-list', 10);
$I->waitForElementVisible('#modal', 5);

// AJAX requests
$I->waitForAjaxLoad();

Screenshots

// Take screenshot
$I->makeScreenshot('product_page');

// Screenshot on failure (automatic in codeception.yml)
$I->makeScreenshot('FAILED_' . $test->getName());

Data Management

Using Fixtures

public function _before(AcceptanceTester $I): void
{
    // Reset database
    $I->resetDatabase();

    // Import fixtures
    $I->importFixture('products.sql');
}

Test Data

Create data providers:

protected function productData(): array
{
    return [
        ['name' => 'Product A', 'price' => 10.00],
        ['name' => 'Product B', 'price' => 20.00],
    ];
}

/**
 * @dataProvider productData
 */
public function createsProduct(AcceptanceTester $I, \Codeception\Example $example): void
{
    $I->amOnPage('/admin/products/new');
    $I->fillField('name', $example['name']);
    $I->fillField('price', $example['price']);
    $I->click('Save');

    $I->see($example['name']);
}

Browser Configuration

Multiple Browsers

# codeception.yml
suites:
    acceptance:
        modules:
            config:
                WebDriver:
                    browser: '%BROWSER%'

# Run with different browsers
BROWSER=chrome vendor/bin/codecept run acceptance
BROWSER=firefox vendor/bin/codecept run acceptance

Headless Mode

WebDriver:
    capabilities:
        chromeOptions:
            args: ['--headless', '--no-sandbox', '--disable-gpu']

Running Tests

Basic Execution

# All acceptance tests
vendor/bin/codecept run acceptance

# Specific test
vendor/bin/codecept run acceptance LoginCest

# Specific method
vendor/bin/codecept run acceptance LoginCest:loginAsBackendUser

# With HTML report
vendor/bin/codecept run acceptance --html

Via runTests.sh

Build/Scripts/runTests.sh -s acceptance

With Docker Compose

# Start services
docker-compose -f Build/docker-compose.yml up -d

# Run tests
vendor/bin/codecept run acceptance

# Stop services
docker-compose -f Build/docker-compose.yml down

Best Practices

  1. Use Page Objects: Reusable page representations
  2. Wait Strategically: Use waitFor* methods for dynamic content
  3. Independent Tests: Each test can run standalone
  4. Descriptive Names: Clear test method names
  5. Screenshot on Failure: Automatic debugging aid
  6. Minimal Setup: Only necessary fixtures and data
  7. Stable Selectors: Use IDs or data attributes, not fragile CSS

Common Pitfalls

No Waits for Dynamic Content

$I->click('Load More');
$I->see('Product 11'); // May fail if AJAX is slow

Proper Waits

$I->click('Load More');
$I->waitForElement('.product-item:nth-child(11)', 5);
$I->see('Product 11');

Brittle Selectors

$I->click('div.container > div:nth-child(3) > button'); // Fragile

Stable Selectors

$I->click('[data-test="add-to-cart"]'); // Stable
$I->click('#product-add-button'); // Better

Large Test Scenarios

// Don't test entire user journey in one test
public function completeUserJourney() { /* 50 steps */ }

Focused Tests

public function addsProductToCart() { /* 5 steps */ }
public function proceedsToCheckout() { /* 7 steps */ }

Debugging

Interactive Mode

vendor/bin/codecept run acceptance --debug
vendor/bin/codecept run acceptance --steps

Pause Execution

$I->pauseExecution(); // Opens interactive shell

HTML Reports

vendor/bin/codecept run acceptance --html
# View report at Tests/Acceptance/_output/report.html

Resources