# cypress-real-events makes Cypress clicks and keystrokes trusted again

> cypress-real-events is a Cypress plugin that fires genuine browser level input events over the Chrome DevTools Protocol instead of dispatching synthetic ones, which restores isTrusted, hover, Tab focus and clipboard behaviour. It is MIT licensed, works only in Chromium based browsers, and needs a real browser installed in CI.

**dmtrKovalenko/cypress-real-events** — Fire native system events from Cypress. 

- Repository: https://github.com/dmtrKovalenko/cypress-real-events
- Stars: 841 · Forks: 73
- Language: HTML
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/dmtrkovalenko-cypress-real-events

## The isTrusted problem, stated plainly

Cypress fires its default events from JavaScript. That means cy.click and cy.type dispatch synthetic DOM events rather than letting the browser produce them, and the consequence is that event.isTrusted is false.

That single fact is the reason this plugin exists. The README is direct that the events can behave a little differently from real native events, and then names the cases where using simulated events is impossible, such as filling a native alert or copying to the clipboard. A component that checks isTrusted and refuses to act on an untrusted event will simply not respond to cy.click, and no amount of retrying or waiting will change that.

There is a second class of problem that has nothing to do with trust checks. Certain behaviours only exist as a consequence of the browser generating the event. The :hover pseudo class is the classic one, since no synthetic event puts the pointer over an element. Native focus management with the Tab key is another, because the browser has to move focus for that to happen at all. The README states that the plugin unlocks features like hovering and native focus management via Tab, and that the event firing system works literally like in puppeteer.

So the plugin is not faster and not flakier by design. It changes what kind of event reaches your application, and that is a different question from whether your selector was right.

## It reuses the CDP connection Cypress already has open

The mechanism is Chrome DevTools Protocol, and the README explains why that is possible.

Cypress is already connected to CDP for its own work, things like taking screenshots and setting the viewport. This project uses that same connection to fire system events. There is no second browser to launch and no sidecar process to manage, which is the difference between this and driving a separate Puppeteer instance from inside a test.

The practical consequence is that the command surface looks like Cypress, not like Puppeteer. The idea stated in the README is that the commands should be as similar as possible to the Cypress default commands, but start with real, so cy.type has cy.realType. The library adds a prefix rather than a different paradigm, which means existing test code can be converted by changing the method name and leaving the chain intact.

Ten commands are listed. cy.realClick, cy.realHover, cy.realPress, cy.realTouch, cy.realType, cy.realSwipe, cy.realMouseDown, cy.realMouseUp, cy.realMouseMove and cy.realMouseWheel. The mouse commands exist separately from the click command because a real click is a press and a release, and a test that needs to hold the button down, for a drag or a long press, needs those two halves individually.

The events are dispatched to the browser, not to your page, which is why they are trusted, and it is also why they land in the top level browser rather than in an iframe or a shadow root, a constraint the README does not discuss.

## Chromium only, and the README does not soften it

The Requirements section is short and blunt. It says Cypress only, really, and then states the limitation that real events work only in the chromium-based browser, which means Firefox is not supported, at least for now.

That is a real constraint for a test suite, and it is worth being clear about how it bites. If your matrix includes Firefox, every test you convert to the real commands has to be skipped there or run against Chromium only, and the skip has to be conditional, which is easy to get wrong. A test that silently falls back to cy.click in Firefox is worse than one that fails loudly, because it stops testing the thing you converted it for.

The note that Cypress itself can fire native events is worth pausing on too. The plugin's justification is the CDP route, and the plugin's own scope is Cypress. If your test runner already exposes trusted events through its own API, adding this dependency buys you nothing and costs you a Chromium-only constraint.

For a suite that runs on Chromium alone, or that runs cross browser but keeps the real events in a Chromium specific job, the constraint is manageable. It is only a problem when the trust requirement and the browser matrix disagree.

## Installing the plugin and registering the commands

The package is on npm, and the install is one line with either package manager.

```bash
npm install cypress-real-events

yarn add cypress-real-events
```

The commands are registered by importing the package in your support file, either cypress/support/e2e.js or e2e.ts, and the same applies to the components files.

```js
import "cypress-real-events";
```

That import is what adds the real commands to Cypress. Without it, cy.realType is simply not defined.

TypeScript users get the declarations by adding the package to the types array in tsconfig.json.

```json
{
  "compilerOptions": {
    "types": ["cypress", "node", "cypress-real-events"]
  }
}
```

The package declares Cypress as a peer dependency rather than a hard one, and the range covers version 4 through 16, so the plugin does not force a particular Cypress version on you and does not pull a second copy into your tree.

A first test looks like this, with the comments giving the intent of each line.

```js
it("tests real events", () => {
  cy.get("input").realClick(); // perform a native real click on the field
  cy.realType("cypress real event"); // fires native system keypress events and fills the field
  cy.realPress("Tab"); // native tab click switches the focus
  cy.get("input").realMouseDown(); // perform a native mouse press on the field
  cy.get("input").realMouseUp(); // perform a native mouse release on the field
  cy.focused().realHover(); // hovers over the new focused element
  cy.contains("some text in the hovered popover");
});
```

## Options are where the real command diverges from cy.click

cy.realClick takes the same kind of options as a native click and adds a few of its own.

```js
cy.get("button").realClick({ position: "topLeft" }); // click on the top left corner of button
cy.get("body").realClick({ x: 100, y: 1240 }); // click by the x & y coordinates relative to the whole window
```

The optional parameters are button, which can be none, left, right, middle, back or forward, pointer, which is mouse or pen, the x and y coordinates, position, which can be any of nine named positions on the element, scrollBehavior, which can be center, top, bottom, nearest or false, and clickCount.

Two details cause confusion. The x and y coordinates are relative to the whole window rather than to the element, and they have a higher priority than position, so specifying both silently ignores the named position. The scrollBehavior option, where false means do not scroll the element into view, is a departure from the Cypress default behaviour and is easy to miss when a click lands on the wrong element.

cy.realHover takes pointer, position and scrollBehavior, and the README notes it can test the :hover preprocessor, which is the point of it.

cy.realPress deserves its own warning. The README says the event is global, which means you must first focus a control before firing it, and it can fire a single key or a shortcut such as Shift+Control+M.

```js
cy.realPress("Tab"); // switch the focus for a11y testing
cy.realPress(["Alt", "Meta", "P"]); // Alt+(Command or Control)+P
```

Forgetting the focus step is the most common reason a real press appears to do nothing, because the key goes to whatever currently has focus in the page.

## CI needs a real browser, and not the cypress-browsers image

Running these commands on a build server has one requirement that catches people out.

The README says that on CI, as with regular Cypress tests, the real commands will work only in the real browser, and to make sure of that you must install a real browser using the cypress install command. If your CI image only carries the Cypress binary, there is no browser for CDP to drive, and the real commands have nothing to talk to.

There is a second, more specific warning, marked as important in the README. The cypress-browsers docker image has issues with running real events commands, with the CDP protocol sometimes not working properly. The recommendation is to use the standard cypress/default or node:lts docker image and install the browser with the cypress install command instead.

That is a concrete piece of operational knowledge you would otherwise have to discover through flaky failures, and it is the kind of detail that a plugin README usually omits.

The rest of the project setup is conventional. The build script runs tsc, lint and format scripts run eslint and prettier over the src and cypress directories, and publishing goes through a build, a version step, a release script in the scripts directory and a publish of the dist folder, with semantic-release configured against a release branch. The devDependencies include typedoc with a markdown plugin, so the API documentation is generated from the TypeScript sources rather than written by hand.

## Versioning that leaves the repository version behind

The release tags tell a fast moving story: v1.15.1 on 2026-09-12, v1.15.0 on 2025-09-05, and v1.14.0 on 2025-01-21. The last push to the repository was on 2026-09-12, the same day as the newest tag, and the repository is not archived.

The package.json in the tree, however, records version 1.9.1. That is not a contradiction so much as a consequence of how the project releases. The release script runs yarn version before publishing, and semantic-release is configured with a release branch, so the version in the published package is decided by the release tooling rather than typed into the file by hand. Anyone reading the source tree will see a version number that does not correspond to what is on npm, and that matters if you are debugging a version mismatch.

The peer dependency range is the safer signal. It spans Cypress 4 through 16, which tells you the plugin has been kept compatible across a very long span of the runner's own releases, and the devDependencies show it currently builds against Cypress 16 with TypeScript 5.

The MIT licence is the simplest part of the picture, with a LICENSE file at the root of the tree.

## Conclusion

cypress-real-events is the right tool when a Cypress test fails only because the browser does not trust the event, for example when a component checks isTrusted, or when hover styling, Tab focus order or clipboard writes cannot be exercised with synthetic events. It is the wrong choice on Firefox, where the plugin does not work at all, or for a suite that is migrating to a different runner, since it is built as a Cypress plugin and depends on Cypress being the one holding the CDP connection. Before adopting it, confirm your tests actually fail for a trust reason rather than a selector reason, install a real browser with cypress install in CI, and avoid the cypress-browsers image, which the README warns has CDP problems.

## FAQ

### How do I install cypress-real-events and register the commands?

Install the package with npm install cypress-real-events or yarn add cypress-real-events, then add import "cypress-real-events" to your cypress/support/e2e.js or e2e.ts file. TypeScript users also add the package name to the types array in tsconfig.json.

### Does cypress-real-events work in Firefox?

No. The README states that real events work only in the chromium-based browser, so Firefox is not supported for now, because the plugin drives the browser through the Chrome DevTools Protocol.

### Which real event commands does the plugin provide?

Ten: cy.realClick, cy.realHover, cy.realPress, cy.realTouch, cy.realType, cy.realSwipe, cy.realMouseDown, cy.realMouseUp, cy.realMouseMove and cy.realMouseWheel. The naming mirrors the Cypress default commands with a real prefix.

### What do I need in CI to run real event commands?

A real browser has to be installed with the cypress install command, because the commands only work in a real browser. The README also warns that the cypress-browsers docker image has problems with the CDP protocol and recommends the standard cypress/default or node:lts image instead.

### Why does my cy.realPress do nothing?

The press event is global, so the README says you must first focus a control before firing it. A real press sends the key to whatever currently has focus in the page.

## Sources

- [dmtrKovalenko/cypress-real-events on GitHub](https://github.com/dmtrKovalenko/cypress-real-events)
- [Issues](https://github.com/dmtrKovalenko/cypress-real-events/issues)
- [License: MIT](https://github.com/dmtrKovalenko/cypress-real-events/blob/main/LICENSE)
- [README](https://github.com/dmtrKovalenko/cypress-real-events/blob/main/README.md)
- [Releases](https://github.com/dmtrKovalenko/cypress-real-events/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dmtrkovalenko-cypress-real-events
