From moodle-dev
Writes and runs Behat acceptance tests for Moodle plugins: feature files, custom step definitions, data generators, JS scenarios, and Selenium setup.
How this skill is triggered — by the user, by Claude, or both
Slash command
/moodle-dev:moodle-behat-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Behat drives a real browser against a dedicated Moodle test site. Features live in `<plugin>/tests/behat/*.feature`. Custom steps go in `tests/behat/behat_<component>.php` extending `behat_base`. Moodle provides hundreds of built-in steps (login, course creation, navigation).
Behat drives a real browser against a dedicated Moodle test site. Features live in <plugin>/tests/behat/*.feature. Custom steps go in tests/behat/behat_<component>.php extending behat_base. Moodle provides hundreds of built-in steps (login, course creation, navigation).
Skip when: writing unit tests for business logic — use moodle-phpunit-testing.
# config.php additions:
$CFG->behat_wwwroot = 'http://localhost:8000';
$CFG->behat_dataroot = '/var/moodledata_behat';
$CFG->behat_prefix = 'beh_';
# Initialize test site:
php admin/tool/behat/cli/init.php
# Install + start a Selenium-compatible driver (one of):
docker run -d -p 4444:4444 selenium/standalone-chrome:latest
# or chromedriver, geckodriver
# Run:
vendor/bin/behat --config /var/moodledata_behat/behat/behat.yml \
--tags @local_example
@local_example @javascript
Feature: Mark attendance
In order to track presence
As a teacher
I need to mark students present
Background:
Given the following "courses" exist:
| fullname | shortname |
| Maths | M101 |
And the following "users" exist:
| username | firstname | lastname |
| teacher1 | Tina | Teach |
| student1 | Sam | Student |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | M101 | editingteacher |
| student1 | M101 | student |
Scenario: Teacher marks a student present
Given I log in as "teacher1"
And I am on "Maths" course homepage
When I follow "Attendance"
And I click on "Mark present" "button" in the "Sam Student" "table_row"
Then I should see "1 present" in the "Today" "fieldset"
Tags:
@<component> — required for --tags filtering@javascript — uses real browser; without it, runs headless (Goutte) — fast but no JS@_file_upload, @_switch_window, @_alert — capability tags so runner can skip on incompatible driversMoodle exposes generators as Behat steps via behat_data_generators:
Given the following "local_example > items" exist:
| course | name | userid |
| M101 | First item | student1 |
To enable, declare in tests/generator/lib.php AND register in tests/behat/behat_local_example.php:
public function get_creatable_entities(): array {
return [
'items' => [
'singular' => 'item',
'datagenerator' => 'item',
'required' => ['name'],
'switchids' => ['course' => 'courseid', 'user' => 'userid'],
],
];
}
Generator method:
// in local_example_generator
public function create_item(array $record): \stdClass {
// same as PHPUnit generator
}
tests/behat/behat_local_example.php:
<?php
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ExpectationException;
class behat_local_example extends behat_base {
/**
* @Given /^there are (\d+) attendance items$/
*/
public function there_are_n_items(int $count): void {
$gen = \testing_util::get_data_generator()
->get_plugin_generator('local_example');
for ($i = 0; $i < $count; $i++) {
$gen->create_item();
}
}
/**
* @Then /^the attendance count should be "(\d+)"$/
*/
public function attendance_count_should_be(int $expected): void {
global $DB;
$actual = $DB->count_records('local_example_items');
if ($actual !== $expected) {
throw new ExpectationException(
"Expected $expected, got $actual",
$this->getSession()
);
}
}
}
Class name must match behat_<component>. After adding/changing steps:
php admin/tool/behat/cli/init.php # re-scan
# All Behat for plugin
vendor/bin/behat --config $CFG->behat_dataroot/behat/behat.yml --tags @local_example
# Single feature
vendor/bin/behat --config ... tests/behat/mark_attendance.feature
# Single scenario (line number)
vendor/bin/behat --config ... tests/behat/mark_attendance.feature:23
# Parallel (4 runners)
php admin/tool/behat/cli/init.php --parallel=4
vendor/bin/moodle_behat_parallel_run --tags @local_example
| Type | Example |
|---|---|
link | I follow "Settings" |
button | I press "Save changes" |
field | I set the field "Name" to "X" |
select | I set the field "Role" to "Manager" |
checkbox | I check "Visible" |
table_row | ... in the "Alice" "table_row" |
fieldset | ... in the "General" "fieldset" |
dialogue | ... in the "Confirm" "dialogue" |
css_element | I click on ".foo .bar" "css_element" |
xpath_element | I click on "//button[@data-x='y']" "xpath_element" |
And I am on the "Course 1" "course" page logged in as "teacher1"
And I navigate to "Users > Enrolment methods" in current course administration
And I should see "X" in the "block_settings" "block"
And I wait until the page is ready
And I wait "2" seconds # avoid; prefer wait-until
And I run the scheduled task "\local_example\task\cleanup"
And I run all adhoc tasks
And the following config values are set as admin:
| config | value | plugin |
| enabled | 1 | local_example |
| Mistake | Fix |
|---|---|
Adding step but not running behat/cli/init.php | Re-init after every PHP step change |
Missing @javascript for AJAX UI | Tag scenario @javascript and run with browser driver |
Hard-coded sleeps I wait 5 seconds | Use I wait until ... exists/disappears |
| Class name mismatch | Must be behat_<component> |
| Not switching to dialogue context | Add in the "..." "dialogue" selector |
Forgetting capability tags (@_file_upload) | Add so runner can skip on incompatible browsers |
Editing behat.yml directly | Regenerated by init.php — config in config.php instead |
# Save HTML+screenshot on failure
$CFG->behat_faildump_path = '/tmp/behat-fails';
# Run a single scenario in foreground browser
vendor/bin/behat --config ... --tags @mytag --stop-on-failure -v
# Pause for inspection
And I should see "this will fail" # forces a wait you can attach to
- name: Behat
run: |
php admin/tool/behat/cli/init.php
vendor/bin/behat --config $MOODLE_DATA/behat/behat.yml \
--tags @local_example --format=progress
npx claudepluginhub saadrahman01/claude-moodle-dev --plugin moodle-devWrite, run, and debug PHPUnit tests for Moodle plugins or core. Covers advanced_testcase, resetAfterTest, data generators, mocking $DB, and testing events/tasks/external functions.
Generates Cucumber BDD tests with Gherkin feature files and step definitions in Java, JavaScript, or Ruby.
Guides BDD testing with Cucumber/Gherkin in Ruby/Rails: feature files, step definitions, hooks, tags, data tables, doc strings, World modules, Capybara integration.