# 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 ```bash composer require --dev codeception/codeception codeception/module-webdriver codeception/module-asserts ``` ### 2. Initialize Codeception ```bash vendor/bin/codecept bootstrap ``` ### 3. Docker Compose Create `Build/docker-compose.yml`: ```yaml 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`: ```yaml 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 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 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: ```php public function loginWithPageObject(AcceptanceTester $I): void { $loginPage = new LoginPage($I); $loginPage->login('admin', 'password'); $I->see('Dashboard'); } ``` ## Common Actions ### Navigation ```php // 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 ```php // 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 ```php // 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 ```php // 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 ```php // Take screenshot $I->makeScreenshot('product_page'); // Screenshot on failure (automatic in codeception.yml) $I->makeScreenshot('FAILED_' . $test->getName()); ``` ## Data Management ### Using Fixtures ```php public function _before(AcceptanceTester $I): void { // Reset database $I->resetDatabase(); // Import fixtures $I->importFixture('products.sql'); } ``` ### Test Data Create data providers: ```php 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 ```yaml # 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 ```yaml WebDriver: capabilities: chromeOptions: args: ['--headless', '--no-sandbox', '--disable-gpu'] ``` ## Running Tests ### Basic Execution ```bash # 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 ```bash Build/Scripts/runTests.sh -s acceptance ``` ### With Docker Compose ```bash # 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** ```php $I->click('Load More'); $I->see('Product 11'); // May fail if AJAX is slow ``` ✅ **Proper Waits** ```php $I->click('Load More'); $I->waitForElement('.product-item:nth-child(11)', 5); $I->see('Product 11'); ``` ❌ **Brittle Selectors** ```php $I->click('div.container > div:nth-child(3) > button'); // Fragile ``` ✅ **Stable Selectors** ```php $I->click('[data-test="add-to-cart"]'); // Stable $I->click('#product-add-button'); // Better ``` ❌ **Large Test Scenarios** ```php // Don't test entire user journey in one test public function completeUserJourney() { /* 50 steps */ } ``` ✅ **Focused Tests** ```php public function addsProductToCart() { /* 5 steps */ } public function proceedsToCheckout() { /* 7 steps */ } ``` ## Debugging ### Interactive Mode ```bash vendor/bin/codecept run acceptance --debug vendor/bin/codecept run acceptance --steps ``` ### Pause Execution ```php $I->pauseExecution(); // Opens interactive shell ``` ### HTML Reports ```bash vendor/bin/codecept run acceptance --html # View report at Tests/Acceptance/_output/report.html ``` ## Resources - [Codeception Documentation](https://codeception.com/docs/) - [WebDriver Module](https://codeception.com/docs/modules/WebDriver) - [Page Objects](https://codeception.com/docs/06-ReusingTestCode#pageobjects) - [TYPO3 Acceptance Testing](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/AcceptanceTests.html)