9.2 KiB
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
- Use Page Objects: Reusable page representations
- Wait Strategically: Use
waitFor*methods for dynamic content - Independent Tests: Each test can run standalone
- Descriptive Names: Clear test method names
- Screenshot on Failure: Automatic debugging aid
- Minimal Setup: Only necessary fixtures and data
- 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