agentsclimarketplace

Moodle behat testing

Skill SaadRahman01/claude-moodle-dev/skills/moodle-behat-testing

Use when writing or running Behat acceptance tests for Moodle plugins. Covers feature files, custom step definitions, tags, data generators in Background, JavaScript scenarios, and Selenium/Chromedriver setup.From its SKILL.md

Install
npx -y skills add SaadRahman01/claude-moodle-dev --skill moodle-behat-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

7.4 KB, as published. Nobody here has run it

Moodle Behat Testing

Overview

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).

When to Use

  • Writing end-to-end / acceptance tests
  • Testing JavaScript-driven UI (drag-drop, modals, AJAX)
  • Reproducing bugs that need full request cycle + cookies + session
  • Adding scenarios to a plugin's regression suite

Skip when: writing unit tests for business logic — use moodle-phpunit-testing.

First-time setup

# 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

Feature file skeleton

@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 drivers

Background data generators

Moodle 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
}

Custom step definitions

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

Running

# 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

Selectors (Mink)

TypeExample
linkI follow "Settings"
buttonI press "Save changes"
fieldI set the field "Name" to "X"
selectI set the field "Role" to "Manager"
checkboxI check "Visible"
table_row... in the "Alice" "table_row"
fieldset... in the "General" "fieldset"
dialogue... in the "Confirm" "dialogue"
css_elementI click on ".foo .bar" "css_element"
xpath_elementI click on "//button[@data-x='y']" "xpath_element"

Useful built-in steps

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 |

Common Mistakes

MistakeFix
Adding step but not running behat/cli/init.phpRe-init after every PHP step change
Missing @javascript for AJAX UITag scenario @javascript and run with browser driver
Hard-coded sleeps I wait 5 secondsUse I wait until ... exists/disappears
Class name mismatchMust be behat_<component>
Not switching to dialogue contextAdd in the "..." "dialogue" selector
Forgetting capability tags (@_file_upload)Add so runner can skip on incompatible browsers
Editing behat.yml directlyRegenerated by init.php — config in config.php instead

Debugging

# 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

CI snippet

- name: Behat
  run: |
    php admin/tool/behat/cli/init.php
    vendor/bin/behat --config $MOODLE_DATA/behat/behat.yml \
      --tags @local_example --format=progress

References

Keep looking

Skills are one crate of 628,971. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.