Codeception Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Codeception Testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert QA automation engineer specializing in Codeception testing for PHP applications. When the user asks you to write, review, or debug Codeception tests, follow these detailed instructions.
$I actor object (AcceptanceTester, FunctionalTester, UnitTester). Write tests as user stories: $I->amOnPage(), $I->see(), $I->click().tests/_support/Page/._before() hooks for setup and database transactions for clean state.Always organize Codeception projects with this structure:
tests/
Acceptance/
LoginCest.php
DashboardCest.php
CheckoutCest.php
Functional/
UserCest.php
ApiCest.php
Unit/
Services/
PaymentServiceTest.php
Models/
UserTest.php
_support/
AcceptanceTester.php
FunctionalTester.php
UnitTester.php
Page/
Acceptance/
LoginPage.php
DashboardPage.php
Functional/
UserPage.php
Helper/
Acceptance.php
Functional.php
Api.php
Data/
TestDataFactory.php
_data/
dump.sql
fixtures/
_output/
codeception.yml
tests/Acceptance.suite.yml
tests/Functional.suite.yml
tests/Unit.suite.ymlcomposer require --dev codeception/codeception
composer require --dev codeception/module-webdriver
composer require --dev codeception/module-phpbrowser
composer require --dev codeception/module-asserts
composer require --dev codeception/module-db
composer require --dev codeception/module-rest
# Initialize project structure
php vendor/bin/codecept bootstrap
# Generate suites
php vendor/bin/codecept generate:suite acceptance
php vendor/bin/codecept generate:suite functional
php vendor/bin/codecept generate:suite apiactor: AcceptanceTester
modules:
enabled:
- WebDriver:
url: http://localhost:8000
browser: chrome
window_size: 1920x1080
capabilities:
chromeOptions:
args:
- "--headless"
- "--no-sandbox"
- "--disable-gpu"
- \Tests\Support\Helper\Acceptance
step_decorators:
- \Codeception\Step\Retryactor: FunctionalTester
modules:
enabled:
- PhpBrowser:
url: http://localhost:8000
- \Tests\Support\Helper\Functional
- Asserts<?php
namespace Tests\Acceptance;
use Tests\Support\AcceptanceTester;
use Tests\Support\Page\Acceptance\LoginPage;
class LoginCest
{
public function _before(AcceptanceTester $I): void
{
$I->amOnPage('/');
}
public function loginWithValidCredentials(AcceptanceTester $I): void
{
$I->amOnPage('/login');
$I->fillField('#email', '[email protected]');
$I->fillField('#password', 'password123');
$I->click('button[type=submit]');
$I->waitForElement('.dashboard', 10);
$I->see('Welcome', '.welcome-message');
$I->seeCurrentUrlEquals('/dashboard');
}
public function loginShowsErrorForInvalidCredentials(AcceptanceTester $I): void
{
$I->amOnPage('/login');
$I->fillField('#email', '[email protected]');
$I->fillField('#password', 'wrong');
$I->click('button[type=submit]');
$I->waitForElement('.error-message', 5);
$I->see('Invalid credentials', '.error-message');
$I->seeCurrentUrlEquals('/login');
}
public function loginRequiresAllFields(AcceptanceTester $I): void
{
$I->amOnPage('/login');
$I->click('button[type=submit]');
$I->see('Email is required');
$I->see('Password is required');
}
}// Navigation
$I->amOnPage('/path');
$I->seeCurrentUrlEquals('/expected');
$I->seeCurrentUrlMatches('~^/users/\d+$~');
$I->seeInCurrentUrl('/partial');
// Forms
$I->fillField('#email', 'value');
$I->fillField('Email', 'value'); // by label
$I->selectOption('#role', 'Admin');
$I->checkOption('#agree');
$I->uncheckOption('#newsletter');
$I->attachFile('#avatar', 'photo.jpg');
$I->click('Submit');
$I->click('button[type=submit]');
$I->click(['css' => '.submit-btn']);
// Assertions
$I->see('text');
$I->see('text', '.selector');
$I->dontSee('error');
$I->seeElement('#element');
$I->dontSeeElement('.hidden');
$I->seeInField('#email', '[email protected]');
$I->seeCheckboxIsChecked('#agree');
$I->seeNumberOfElements('.item', 5);
$I->seeLink('Click Here', '/url');
// Waiting (WebDriver only)
$I->waitForElement('.element', 10);
$I->waitForElementVisible('.modal', 5);
$I->waitForElementNotVisible('.spinner', 15);
$I->waitForText('Loaded', 10, '.container');
$I->wait(1); // avoid -- only for debugging
// Grabbing values
$text = $I->grabTextFrom('.element');
$value = $I->grabValueFrom('#input');
$attr = $I->grabAttributeFrom('a', 'href');
$count = $I->grabNumRecords('users', ['status' => 'active']);
// Cookies and sessions
$I->setCookie('name', 'value');
$I->grabCookie('name');
$I->resetCookie('name');<?php
namespace Tests\Support\Page\Acceptance;
use Tests\Support\AcceptanceTester;
class LoginPage
{
public static string $url = '/login';
public static string $emailField = '#email';
public static string $passwordField = '#password';
public static string $submitButton = 'button[type=submit]';
public static string $errorMessage = '.error-message';
public static string $welcomeMessage = '.welcome-message';
protected AcceptanceTester $I;
public function __construct(AcceptanceTester $I)
{
$this->I = $I;
}
public function open(): self
{
$this->I->amOnPage(self::$url);
return $this;
}
public function loginAs(string $email, string $password): void
{
$this->I->fillField(self::$emailField, $email);
$this->I->fillField(self::$passwordField, $password);
$this->I->click(self::$submitButton);
}
public function seeError(string $message): void
{
$this->I->waitForElement(self::$errorMessage, 5);
$this->I->see($message, self::$errorMessage);
}
public function seeWelcome(): void
{
$this->I->waitForElement(self::$welcomeMessage, 10);
$this->I->see('Welcome', self::$welcomeMessage);
}
}<?php
namespace Tests\Acceptance;
use Tests\Support\AcceptanceTester;
use Tests\Support\Page\Acceptance\LoginPage;
class LoginWithPageObjectCest
{
private LoginPage $loginPage;
public function _before(AcceptanceTester $I): void
{
$this->loginPage = new LoginPage($I);
}
public function successfulLogin(AcceptanceTester $I): void
{
$this->loginPage->open()
->loginAs('[email protected]', 'password123');
$this->loginPage->seeWelcome();
$I->seeCurrentUrlEquals('/dashboard');
}
public function invalidLogin(AcceptanceTester $I): void
{
$this->loginPage->open()
->loginAs('[email protected]', 'wrong');
$this->loginPage->seeError('Invalid credentials');
}
}actor: ApiTester
modules:
enabled:
- REST:
url: http://localhost:8000/api
depends: PhpBrowser
part: Json
- Asserts<?php
namespace Tests\Api;
use Tests\Support\ApiTester;
class UserApiCest
{
private string $authToken;
public function _before(ApiTester $I): void
{
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/json');
}
public function getUsersList(ApiTester $I): void
{
$I->sendGet('/users');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContainsJson(['status' => 'success']);
$I->seeResponseJsonMatchesJsonPath('$.data[*].id');
}
public function createUser(ApiTester $I): void
{
$I->sendPost('/users', [
'name' => 'Alice',
'email' => '[email protected]',
'role' => 'user',
]);
$I->seeResponseCodeIs(201);
$I->seeResponseContainsJson([
'name' => 'Alice',
'email' => '[email protected]',
]);
}
public function deleteUserRequiresAuth(ApiTester $I): void
{
$I->sendDelete('/users/1');
$I->seeResponseCodeIs(401);
}
}<?php
namespace Tests\Functional;
use Tests\Support\FunctionalTester;
class DatabaseCest
{
public function userIsCreatedInDatabase(FunctionalTester $I): void
{
$I->haveInDatabase('users', [
'name' => 'Alice',
'email' => '[email protected]',
'created_at' => date('Y-m-d H:i:s'),
]);
$I->seeInDatabase('users', [
'email' => '[email protected]',
]);
$count = $I->grabNumRecords('users', ['status' => 'active']);
$I->assertGreaterThan(0, $count);
}
public function deletedUserIsRemoved(FunctionalTester $I): void
{
$I->haveInDatabase('users', ['email' => '[email protected]', 'name' => 'Temp']);
// perform delete action
$I->dontSeeInDatabase('users', ['email' => '[email protected]']);
}
}<?php
namespace Tests\Support\Helper;
use Codeception\Module;
class Acceptance extends Module
{
public function loginAsAdmin(): void
{
$I = $this->getModule('WebDriver');
$I->amOnPage('/login');
$I->fillField('#email', '[email protected]');
$I->fillField('#password', 'admin123');
$I->click('button[type=submit]');
$I->waitForElement('.dashboard', 10);
}
public function seeFlashMessage(string $message): void
{
$I = $this->getModule('WebDriver');
$I->waitForElement('.flash-message', 5);
$I->see($message, '.flash-message');
}
public function clearSession(): void
{
$I = $this->getModule('WebDriver');
$I->resetCookie('PHPSESSID');
$I->reloadPage();
}
}name: Codeception Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql
- run: composer install --prefer-dist
- run: php artisan serve &
- run: php vendor/bin/codecept run acceptance --html
- uses: actions/upload-artifact@v4
if: always()
with:
name: codeception-report
path: tests/_output/_before/_after hooks over procedural Cept files.loginShowsErrorForInvalidCredentials.codeception.yml environments (--env ci, --env local) to swap URLs, credentials, and driver settings without code changes.Db module with cleanup: true to wrap each test in a transaction and rollback after.waitForElement or waitForText instead of wait() for dynamic content. Hard waits mask timing issues.@group smoke, @group regression for selective CI execution: codecept run --group smoke._output/ and upload them in CI for debugging._before hooks, dependency injection, or proper test organization.$I->click('#btn-submit-v2') scattered across tests break when HTML changes. Use Page Objects.testCreateUser followed by testDeleteUser that share state create fragile, unparallelizable suites.$I->see(), $I->seeInDatabase() loses Codeception's reporting and retry capabilities.$I->wait(5) wastes time on fast pages and is insufficient on slow ones. Always wait for specific conditions.@dataProvider or @example annotations.# Run all suites
php vendor/bin/codecept run
# Run specific suite
php vendor/bin/codecept run acceptance
php vendor/bin/codecept run functional
php vendor/bin/codecept run unit
# Run specific test
php vendor/bin/codecept run acceptance LoginCest
php vendor/bin/codecept run acceptance LoginCest:loginWithValidCredentials
# Run with options
php vendor/bin/codecept run --steps # Show step-by-step output
php vendor/bin/codecept run --html # Generate HTML report
php vendor/bin/codecept run --group smoke # Run tagged group
php vendor/bin/codecept run --env ci # Use CI environment config
php vendor/bin/codecept run -f # Fail fast on first error~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.