npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tappet/cypress

v0.2.1

Published

Enjoyable GUI testing with Tappet, using Cypress

Readme

Tappet - Enjoyable GUI testing with Tappet, using Cypress

Build Status

[EXPERIMENTAL] Cypress adapter for Tappet GUI testing.

See the Tappet core README for the core Tappet concepts - arrange/act/assert specs, fixtures, pages, and in particular transitions, the mechanism used to detect and assert on whole-page-state changes such as navigation or a modal opening/closing. This README only covers what's specific to the Cypress adapter, including how custom transition detection is wired up here.

Installation

Prerequisites

1. Install the adapter

composer require --dev tappet/cypress
npm install --save-dev cypress @tappet/cypress

2. Configure the adapter

Create tappet.{suite-name}.suite.php in the root of your project, for example:

tappet.cypress.suite.php (to name the suite cypress):

<?php

declare(strict_types=1);

use Tappet\Cypress\Suite\CypressSuite;

$suite = new CypressSuite(
    cypressRoot: __DIR__ . '/tests/gui' // Points to the nested Cypress project created for Tappet.
);

$suite->addPlugin(new MyPlugin());

return $suite;

3. Install Cypress

node_modules/.bin/cypress install

mkdir -p tests/gui

Inside the nested Cypress project directory referenced by cypressRoot above (e.g. tests/gui), scaffold a Cypress project along with this package's JS/TS half, which provides the Uniter defineConfig factory and Cypress plugin that bridge PHP and JavaScript:

cd tests/gui

# Cypress does not support an "init" CLI command: instead, run `open` once to scaffold a Cypress project.
# Continue until you get to the "We added the following files to your project:" stage, then you can close Cypress.
../../node_modules/.bin/cypress open

# By this point, the file `cypress.config.js` should exist:
ls -l cypress.config.js

Update cypress.config.js to register tappet/cypress:

cypress.config.js

const { defineConfig } = require('cypress');
const { register: tappetPlugin } = require('@tappet/cypress/cypress/plugin');

module.exports = defineConfig({
    e2e: {
        setupNodeEvents(on, config) {
            // Configure Cypress to run Tappet specs.
            return tappetPlugin(on, config);
        },
        // Path to where your Tappet PHP spec files will live.
        specPattern: 'spec/**/*.spec.php',
    },
    // Other config, e.g.:
    defaultCommandTimeout: 20000,
    pageLoadTimeout: 60000,
});

In the same directory, create a uniter.config.js to configure Uniter, which is used to run PHP code under a JavaScript environment:

uniter.config.js

const { defineConfig } = require('@tappet/cypress/uniter/defineConfig');

/*
 * Configures Uniter, which is a PHP-to-JavaScript transpiler.
 *
 * This configuration allows Cypress to run Tappet specs written in PHP.
 */
module.exports = defineConfig(__dirname, '../../', {
    include: [
        // If your suite is named "cypress".
        'tappet.cypress.suite.php',
        // It is recommended to .gitignore this `.local.php` file: it allows you to keep local overrides
        // for the config that you do not want to commit, e.g. local dev environment URLs.
        'tappet.cypress.suite.local.php',
        'tests/gui/app/**/*.php',

        // Include any other needed PHP files here, including those from `vendor/`.
    ],
});

Now you can create your specs, for example tests/gui/spec/Workflow/create_workflow.spec.php. Note that the spec/ part of the path was defined by specPattern: 'spec/**/*.spec.php' in cypress.config.js above.

Custom transition detection

Tappet core defines what a transition is (TransitionInterface) and how specs assert on one (EnvironmentInterface::assertTransition()). However, detection of a transition occurring is necessarily adapter-specific, since it depends on hooking into the underlying test tool. This package detects transitions by dispatching events during the Cypress test run that plugins can listen to, and pushing detected transitions onto the shared transition log via CypressAutomationInterface::pushTransition(TransitionInterface $transition): void.

The built-in NavigationTransition is pushed automatically by this adapter whenever the AUT's window fires a load event; for plain page navigation, there is nothing you need to wire up. Anything else, such as a modal appearing or disappearing, needs a plugin.

Writing a plugin

A plugin implements Tappet\Suite\Plugin\PluginInterface (from tappet/tappet core) and declares a map of event class to listener. The most relevant event for transition detection is WindowBeforeLoadEvent, fired before each AUT window load with access to $window, a Uniter FFI wrapper of the browser's Window object so that JS methods/properties (e.g. $window->document, $window->MutationObserver) can be called/used from PHP.

Continuing the modal example from the core README, here's how tappet/tappet's own test suite detects data-ui-modal elements toggling their hidden attribute, using a MutationObserver, and pushes the corresponding ModalOpenTransition/ModalClosedTransition:

class TestPlugin implements PluginInterface
{
    public function getListeners(): array
    {
        return [
            WindowBeforeLoadEvent::class => function (EventInterface $event, CypressAutomationInterface $automation): void {
                $window = $event->getWindow();

                $observer = new $window->MutationObserver(
                    function ($mutations) use ($automation): void {
                        foreach ($mutations as $mutation) {
                            if ($mutation->type !== 'attributes' || $mutation->attributeName !== 'hidden') {
                                return;
                            }

                            $el = $mutation->target;
                            $handle = $el->getAttribute('data-ui-modal');

                            if (!$handle) {
                                return;
                            }

                            $isVisible = !$el->hasAttribute('hidden');

                            $automation->pushTransition(
                                $isVisible
                                    ? new ModalOpenTransition($handle)
                                    : new ModalClosedTransition($handle)
                            );
                        }
                    }
                );

                $window->document->addEventListener(
                    'DOMContentLoaded',
                    function () use ($observer, $window): void {
                        $observer->observe($window->document->body, [
                            'subtree' => true,
                            'attributes' => true,
                            'attributeFilter' => ['hidden'],
                        ]);
                    }
                );
            },
        ];
    }
}

ModalOpenTransition/ModalClosedTransition and their matching ExpectModalOpen/ExpectModalClosed arrangement/assertion classes are just application-level TransitionInterface/ArrangementInterface/ AssertionInterface implementations - see the core README's transitions section for how those interfaces work. Nothing about them is Cypress-specific; only the detection plugin above is.

Registering a plugin

Register the plugin against the CypressSuite instance returned by tappet.{suite-name}.suite.php:

<?php

declare(strict_types=1);

use Tappet\Cypress\Suite\CypressSuite;

$suite = new CypressSuite(__DIR__);

$suite->addPlugin(new TestPlugin());

return $suite;

Custom behaviour extension

Other *InitEvent classes in Tappet\Cypress\Event (FieldActionInitEvent, FieldAssertionInitEvent, InteractionInitEvent, MatchHandlerInitEvent, RegionAssertionInitEvent, StateAssertionInitEvent) follow the same plugin-listener mechanism, but are for registering custom field actions, field assertions, interactions, matchers, matchers, region assertions and state assertions respectively rather than for transition detection.

For example:

<?php

declare(strict_types=1);

namespace MyApp\Tests\Tappet\Plugin;

use MyApp\Tests\Tappet\MatchHandler\BadgeMatchHandler;
use Tappet\Common\Event\EventInterface;
use Tappet\Cypress\Automation\CypressAutomationInterface;
use Tappet\Cypress\Event\MatchHandlerInitEvent;
use Tappet\Suite\Plugin\PluginInterface;

/**
 * @implements PluginInterface<CypressAutomationInterface>
 */
class MyPlugin implements PluginInterface
{
    public function getListeners(): array
    {
        return [
            MatchHandlerInitEvent::class => function (EventInterface $event): void {
                /** @var MatchHandlerInitEvent $event */
                $event->registerMatchHandler('badge', new BadgeMatchHandler($event->getAutomation()));
            },
        ];
    }
}