PHPackages                             aklump/dom-testing-selectors - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. aklump/dom-testing-selectors

ActiveLibrary[Testing &amp; Quality](/categories/testing)

aklump/dom-testing-selectors
============================

This library offers a PHP solution designed to add specific markup to your DOM for testing purposes.

0.0.14(1y ago)037BSD-3-ClausePHPPHP &gt;=7.3

Since Aug 28Pushed 1y ago1 watchersCompare

[ Source](https://github.com/aklump/dom-testing-selectors)[ Packagist](https://packagist.org/packages/aklump/dom-testing-selectors)[ Docs](https://github.com/aklump/dom-testing-selectors)[ Fund](https://www.buymeacoffee.com/aklump)[ RSS](/packages/aklump-dom-testing-selectors/feed)WikiDiscussions main Synced yesterday

READMEChangelogDependencies (2)Versions (15)Used By (0)

DOM Testing Selectors
=====================

[](#dom-testing-selectors)

[![hero](images/testing_selectors.jpg)](images/testing_selectors.jpg)

[![Packagist Page](https://camo.githubusercontent.com/dac693ae314ec7807dbc72ae40ed573399e15265048b70f2ec146959ebc9eef2/68747470733a2f2f62616467656e2e6e65742f7061636b61676973742f6e616d652f616b6c756d702f646f6d2d74657374696e672d73656c6563746f7273)](https://www.packagist.org/packages/aklump/dom-testing-selectors) [![](https://camo.githubusercontent.com/1b2b046345901be7ddad85420f88bdab509bc37dfe7f79566a42370938204b61/68747470733a2f2f62616467656e2e6e65742f7061636b61676973742f7068702f616b6c756d702f646f6d2d74657374696e672d73656c6563746f7273)](https://camo.githubusercontent.com/1b2b046345901be7ddad85420f88bdab509bc37dfe7f79566a42370938204b61/68747470733a2f2f62616467656e2e6e65742f7061636b61676973742f7068702f616b6c756d702f646f6d2d74657374696e672d73656c6563746f7273) [![](https://camo.githubusercontent.com/4d8221b06936a85571120cc8e15edc794b306a20c1dfae062482a9aac2e0db16/68747470733a2f2f62616467656e2e6e65742f6769746875622f6c6963656e73652f616b6c756d702f646f6d2d74657374696e672d73656c6563746f7273)](https://camo.githubusercontent.com/4d8221b06936a85571120cc8e15edc794b306a20c1dfae062482a9aac2e0db16/68747470733a2f2f62616467656e2e6e65742f6769746875622f6c6963656e73652f616b6c756d702f646f6d2d74657374696e672d73656c6563746f7273)

**This library offers a PHP solution designed to add specific markup to your DOM for testing purposes.** By default, it configures a `data-test` attribute for DOM elements, as shown in the examples. The library is intended to be integrated into a server-side rendering pipeline, which generates your markup.

```

```

This attribute should be used exclusively for targeting elements in your tests. Separating concerns in this way helps prevent test fragility that can arise from relying on classes or other multipurpose attributes that you may not control and could change unexpectedly. By using a dedicated data attribute for testing—one that you control—you can ensure that your tests remain stable and reliable over time.

```
describe('The main page', () => {
  it('should have hero and article sections.', () => {
    cy.visit('/')
    cy.get('[data-test="hero"]').its('length').should('equal', 1)
    cy.get('[data-test="article"]').its('length').should('equal', 1)
  })
})
```

Install with Composer
---------------------

[](#install-with-composer)

1. Require this package:

    ```
    composer require aklump/dom-testing-selectors:^0.0

    ```

Selectors
---------

[](#selectors)

```
$test_selector = new \AKlump\DomTestingSelectors\Selector\DataTestSelector();

$username_selector = $test_selector('username', '');
// $username_selector === 'data-test="username"'

$password_selector = $test_selector('password', '');
// $password_selector === 'data-test="password"'
```

You may also add a group to the selectors, which will prefix the attribute value:

```
$test_selector->setGroup('login');

$username_selector = $test_selector('username', '');
// $username_selector === 'data-test="login__username"'
```

#### Naming Convention

[](#naming-convention)

Be aware that in an effort to reduce confusion and errors, the provided `\AKlump\DomTestingSelectors\Selector\AbstractSelector` has an opinion about naming convention.

```
$selector = new \AKlump\DomTestingSelectors\Selector\DataTestSelector();
$attribute = $selector('A.StrangeSelector string---NAME', '');
// $attribute === 'data-test="a_strange_selector_string_name"
```

To change this behavior you should create a custom selector class, overriding `\AKlump\DomTestingSelectors\Selector\AbstractSelector::applyNamingConventions`.

#### Using a Custom Attribute

[](#using-a-custom-attribute)

This example will illustrate how to change the attribute to `data-cy`, which you may want to use while [testing with Cypress](https://www.cypress.io/). Simple create a custom selector class and use in place of `DataTestSelector`.

```
namespace Vendor\DomTestingSelectors\Selectors;

final class CypressSelector extends AbstractSelector {

  public function getAttributeName(): string {
    return 'data-cy';
  }
}
```

To learn more about selectors, refer to `\AKlump\DomTestingSelectors\Selectors\AbstractSelector`.

#### Using Classes as Testing Selectors

[](#using-classes-as-testing-selectors)

The included `\AKlump\DomTestingSelectors\Selector\ClassSelector` can be used instead of `\AKlump\DomTestingSelectors\Selector\DataTestSelector` if you want to use CSS classes (e.g. `t-foo`) for selecting your elements. **Notice the naming convention is altered for classes, using hyphens instead of underscores.**

```
// When using the class attribute--e.g., ""--you must
// merge with any existing value.  The current value has to be passed as the
// second argument to __invoke() and __getAttributeValue.
$selector = new \AKlump\DomTestingSelectors\Selector\ClassSelector();
$attribute_markup = $selector('my_target_element', 'foo bar');
// $attribute_markup === 'class="foo bar t-my-target-element"'

$selector = new \AKlump\DomTestingSelectors\Selector\ClassSelector();
$attribute_value = $selector->setName('my_target_element')
  ->getAttributeValue('foo bar');
// $attribute_value === 'foo bar t-my-target-element'
```

Handlers
--------

[](#handlers)

Handlers do the work of adding the selector markup to your HTML. The `\AKlump\DomTestingSelectors\Handlers\StringHandler` is provided by this library, to add the selector to HTML strings.

```
$element = '';
$handler = new \AKlump\DomTestingSelectors\Handler\StringHandler();
$selector = new \AKlump\DomTestingSelectors\Selector\DataTestSelector();
if ($handler->canHandle($element)) {
  $handler->setTestingSelectorOnElement($element, $selector->setName('foobar'));
}
// $element === ''
```

Framework-specific and custom handlers are very easy to add by implementing `\AKlump\DomTestingSelectors\Handler\HandlerInterface`.

```
class MyArrayHandler implements \AKlump\DomTestingSelectors\Handler\HandlerInterface {
  public function canHandle($element): bool {
    return is_array($element);
  }

  public function setTestingSelectorOnElement(&$element, \AKlump\DomTestingSelectors\Selector\ElementSelectorInterface $selector): void {
    $attribute_name = $selector->getAttributeName();
    $current_value = $element['attributes'][$attribute_name] ?? '';
    if ('class' === $attribute_name) {
      if (is_array($current_value)) {
        // Current value must be a string, and "class" should be an array.  If
        // so, cast it here.
        $current_value = implode(' ', $current_value);
      }
      $element['attributes'][$attribute_name] = [$selector->getAttributeValue($current_value)];
    }
    else {
      $element['attributes'][$attribute_name] = $selector->getAttributeValue($current_value);
    }
  }
}
```

Factories
---------

[](#factories)

In practice you may have multiple handlers to cover the full range of elements you wish to markup. This is the reason for `\AKlump\DomTestingSelectors\AbstractHandlerFactory`. The following example shows how you would write and use a custom factory.

```
class MyFactory extends \AKlump\DomTestingSelectors\Factory\AbstractHandlerFactory {
  public function __construct() {
    $this->addHandler(new \AKlump\DomTestingSelectors\Handler\StringHandler());
    $this->addHandler(new MyArrayHandler());
  }
}

$factory = new MyFactory();
$selector = new \AKlump\DomTestingSelectors\Selector\DataTestSelector();
$selector->setName('foobar');

$element1 = '';
$element2 = ['tag' => 'div'];

try {
  $factory->getHandler($element1)->setTestingSelectorOnElement($element1, $selector);
  // $element1 === ''

  $factory->getHandler($element2)->setTestingSelectorOnElement($element2, $selector);
  // $element2 === ['tag'=>'div','attributes'=>['data-test'=>'foobar']]
}
catch (\AKlump\DomTestingSelectors\Exception\NoHandlerFoundException $exception) {
  // No handler found
}
```

### Make it a Safe Factory

[](#make-it-a-safe-factory)

Notice that you have to catch if a handler cannot be found for a given element. To get around this you may want to create a "safe factory", that is one which adds **as it's final handler** the `\AKlump\DomTestingSelectors\Handler\PassThroughHandler` or something similar (maybe with your own logging). Instead of an exception, the $element will simply pass through unchanged.

```
class MySafeFactory extends \AKlump\DomTestingSelectors\Factory\AbstractHandlerFactory {

  public function __construct() {
    $this->addHandler(new \AKlump\DomTestingSelectors\Handler\StringHandler());
    $this->addHandler(new \AKlump\DomTestingSelectors\Handler\PassThroughHandler());
  }
}

$factory = new MySafeFactory();
$selector = new \AKlump\DomTestingSelectors\Selector\DataTestSelector();
$selector->setName('foobar');

$element1 = '';
$element2 = 'lorem ipsum dolar';

$factory->getHandler($element1)->setTestingSelectorOnElement($element1, $selector);
// $element1 === ''

$factory->getHandler($element2)->setTestingSelectorOnElement($element2, $selector);
// $element2 === 'lorem ipsum dolar'
```

Sponsor this project
--------------------

[](#sponsor-this-project)

 [github.com](https://github.com/sponsors/aklump)

 [buymeacoffee.com](https://buymeacoffee.com/aklump)

 [paypal.com](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4E5KZHDQCEUV8&item_name=Open%20Source%20Sponsorship)

###  Health Score

22

—

LowBetter than 21% of packages

Maintenance37

Infrequent updates — may be unmaintained

Popularity7

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Recently: every ~0 days

Total

14

Last Release

569d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/79476f3b82f9c766433c7eb401cbcfc636cd5d5ccf2b2e6b50ea81d1de6c2730?d=identicon)[aklump](/maintainers/aklump)

---

Top Contributors

[![aklump](https://avatars.githubusercontent.com/u/425737?v=4)](https://github.com/aklump "aklump (53 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/aklump-dom-testing-selectors/health.svg)

```
[![Health](https://phpackages.com/badges/aklump-dom-testing-selectors/health.svg)](https://phpackages.com/packages/aklump-dom-testing-selectors)
```

###  Alternatives

[dms/phpunit-arraysubset-asserts

This package provides ArraySubset and related asserts once deprecated in PHPUnit 8

14429.2M361](/packages/dms-phpunit-arraysubset-asserts)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
