# Drupal Functionaljs Test

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-trebormc-drupal-ai-agents-drupal-functionaljs-test`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [trebormc](https://agentstack.voostack.com/s/trebormc)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [trebormc](https://github.com/trebormc)
- **Source:** https://github.com/trebormc/drupal-ai-agents/tree/main/.claude/skills/drupal-functionaljs-test

## Install

```sh
agentstack add skill-trebormc-drupal-ai-agents-drupal-functionaljs-test
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Drupal FunctionalJavascript Test

## What It Is

Extends BrowserTestBase but uses a real Chrome browser via WebDriver.
JavaScript executes completely. AJAX works. CSS animations are disabled.

Speed: 10-170 seconds per class. Use ONLY when there is no alternative.

## Critical Difference from BrowserTestBase

`statusCodeEquals()` does NOT work in WebDriverTestBase. The Selenium2 driver does
not have access to HTTP status codes. Verify access with `pageTextContains()` or
`elementExists()`.

## Base Template

```php
adminUser = $this->drupalCreateUser(['access content', 'administer MODULE']);
  }

  public function testAjaxInteraction(): void {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('route');

    $page = $this->getSession()->getPage();
    $assert = $this->assertSession();

    $page->selectFieldOption('field', 'value');
    $assert->assertWaitOnAjaxRequest();

    $element = $assert->waitForElementVisible('css', '.result');
    $this->assertNotEmpty($element);
  }

}
```

## GOLDEN RULE: Never sleep(), Always Waits

The #1 cause of flaky tests. NEVER `sleep()`. ALWAYS use waits:

```php
$assert = $this->assertSession();

// Wait for AJAX (most used)
$assert->assertWaitOnAjaxRequest();

// Wait for element in DOM
$element = $assert->waitForElement('css', '.my-element');
$this->assertNotEmpty($element);

// Wait for VISIBLE element
$element = $assert->waitForElementVisible('css', '.dropdown');

// Wait for element to disappear
$assert->waitForElementRemoved('css', '.spinner');

// Specific waits
$assert->waitForButton('Submit');
$assert->waitForLink('Next');
$assert->waitForField('field_name');
$assert->waitForId('my-id');
$assert->waitForText('Done');

// Autocomplete
$assert->waitOnAutocomplete();

// Custom JS condition
$this->getSession()->wait(5000, 'jQuery("#el").is(":visible")');
```

## Pattern: Form with AJAX

```php
public function testDependentSelect(): void {
  $this->drupalLogin($this->adminUser);
  $this->drupalGet('node/add/article');

  $page = $this->getSession()->getPage();
  $assert = $this->assertSession();

  $page->selectFieldOption('field_country', 'ES');
  $assert->assertWaitOnAjaxRequest();

  $cityField = $assert->waitForElementVisible('css', '#edit-field-city');
  $this->assertNotEmpty($cityField);

  $options = $cityField->findAll('css', 'option');
  $values = array_map(fn($o) => $o->getValue(), $options);
  $this->assertContains('madrid', $values);
}
```

## Pattern: Autocomplete (Entity Reference)

```php
public function testAutocomplete(): void {
  $this->drupalCreateNode(['type' => 'article', 'title' => 'Drupal Testing Guide']);

  $this->drupalLogin($this->adminUser);
  $this->drupalGet('node/add/page');

  $page = $this->getSession()->getPage();
  $assert = $this->assertSession();

  $field = $page->findField('field_related[0][target_id]');
  $field->setValue('Drupal');
  $assert->waitOnAutocomplete();

  $suggestions = $page->findAll('css', '.ui-autocomplete li');
  $this->assertGreaterThanOrEqual(1, count($suggestions));
  $suggestions[0]->click();

  $this->assertStringContainsString('Drupal Testing Guide', $field->getValue());
}
```

## Pattern: Modal / Dialog

```php
public function testModal(): void {
  $this->drupalLogin($this->adminUser);
  $this->drupalGet('admin/structure/block');

  $page = $this->getSession()->getPage();
  $assert = $this->assertSession();

  $page->clickLink('Place block');
  $modal = $assert->waitForElementVisible('css', '.ui-dialog');
  $this->assertNotEmpty($modal);

  $modal->fillField('Filter', 'Powered by');
  $assert->waitForText('Powered by Drupal');

  $modal->pressButton('Close');
  $assert->waitForElementRemoved('css', '.ui-dialog');
}
```

## Pattern: Visibility with #states

```php
public function testConditionalVisibility(): void {
  $this->drupalLogin($this->adminUser);
  $this->drupalGet('my-module/settings');

  $page = $this->getSession()->getPage();

  $field = $page->findField('api_key');
  $this->assertFalse($field->isVisible());

  $page->checkField('enable_api');
  $this->assertTrue($field->isVisible());
}
```

## Extra Capabilities

```php
// Verify visibility (not possible in BrowserTestBase)
$this->assertFalse($element->isVisible());

// Execute JS
$this->getSession()->executeScript('document.title = "Test"');

// Evaluate JS (returns value)
$result = $this->getSession()->evaluateScript('return document.title');

// Browser drupalSettings
$settings = $this->getDrupalSettings();

// Screenshot for debug
$this->createScreenshot('/tmp/debug.png');
```

## ChromeDriver Config

Drupal 10.3+ and 11:
```xml

```

Version guard: Drupal 10 (PHPUnit 9) still accepts plain `chromeOptions` (deprecated since 10.3); in Drupal 11 `goog:chromeOptions` is MANDATORY (without the `goog:` prefix it does not work). When in doubt, use `goog:chromeOptions` — it works on both.

## Anti-Patterns

1. Using `sleep()`. NEVER.
2. Using `statusCodeEquals()`. DOES NOT WORK in WebDriverTestBase.
3. Not waiting after AJAX. Will pass locally and fail in CI.
4. Tests with 15+ interactions. That is an E2E flow -> Behat or Playwright.
5. Testing things that do not need JS. Use Functional test.

## Running Tests

ChromeDriver is already available inside the DDEV web container. Do not start it
from the agent container (it would not be reachable by PHPUnit in web).

First pick the config form ONCE per session: `ssh web test -f phpunit.xml && echo "ROOT" || echo "CORE"`

```bash
# Form ROOT (project phpunit.xml exists — it must define MINK_DRIVER_ARGS_WEBDRIVER):
ssh web ./vendor/bin/phpunit $DDEV_DOCROOT/modules/custom/MODULE/tests/src/FunctionalJavascript

# Form CORE (no project phpunit.xml — pass ALL env vars explicitly, including the webdriver):
ssh web env SIMPLETEST_DB=mysql://db:db@db/db SIMPLETEST_BASE_URL=http://localhost \
  MINK_DRIVER_ARGS_WEBDRIVER='["chrome", {"browserName":"chrome","goog:chromeOptions":{"args":["--disable-gpu","--headless","--no-sandbox","--disable-dev-shm-usage"]}}, "http://127.0.0.1:9515"]' \
  ./vendor/bin/phpunit -c $DDEV_DOCROOT/core $DDEV_DOCROOT/modules/custom/MODULE/tests/src/FunctionalJavascript
```

## Troubleshooting

| Error | Cause | Fix |
|-------|-------|-----|
| Connection refused to ChromeDriver | Driver URL wrong or driver not running | Check the URL in MINK_DRIVER_ARGS_WEBDRIVER; verify the driver runs in the web container |
| Test flaky (passes sometimes) | Race condition after AJAX/JS | Replace any `sleep()` with `assertWaitOnAjaxRequest()` or `waitForElementVisible()` |
| `statusCodeEquals()` fails/undefined | Not supported in WebDriverTestBase | Assert on page text/elements instead |
| Chrome capability errors on D11 | Plain `chromeOptions` used | Use `goog:chromeOptions` |

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [trebormc](https://github.com/trebormc)
- **Source:** [trebormc/drupal-ai-agents](https://github.com/trebormc/drupal-ai-agents)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-trebormc-drupal-ai-agents-drupal-functionaljs-test
- Seller: https://agentstack.voostack.com/s/trebormc
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
