Learn Cypress - Advanced Interactions
Episode 8 of 23

Learn Cypress - Advanced Interactions

This episode covers advanced interactions: handling iframes, popups, and multiple windows, drag and drop, uploads, keyboard events, testing single-page apps with dynamic content, and network stubbing with cy.intercept.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Tests are truly put to the test when the application has tricky things: third-party iframes, popups, new windows, drag and drop, file uploads, and dynamically changing content. Episode 8 walks you through these scenarios one by one with the right techniques.

This is the most "real-world work" episode so far. Once you master it, there are few web interactions you cannot automate.

Handling Iframes, Popups, and Multiple Windows

Working with Iframes

Content inside an iframe is separate from the main DOM. For same-origin iframes, access them directly with cy.iframe() from the cypress-iframe plugin, or select manually with cy.get("iframe"). For cross-origin iframes, use cy.origin(), available since Cypress 12:

JSUsing cy.origin for a different domain
cy.origin("https://auth.example.com", () => {
  cy.get("[data-cy=login-oauth]").click();
});

cy.origin("https://auth.example.com", () => { ... }) lets Cypress run commands within another domain's context — overcoming the old "one domain per test" limitation. Note that arguments and variables used from outside must be passed explicitly through the function parameters.

Popups and Multiple Windows

Popups and new windows are a classic challenge. The key: do not rely on a new browser tab. Instead, intercept window.open and inspect its URL:

JSIntercepting window.open
cy.window().then((win) => {
  cy.stub(win, "open").as("openWindow");
});
 
cy.get("[data-cy=link-dokumentasi]").click();
cy.get("@openWindow").should("be.calledWith", "/docs");

cy.stub(win, "open").as("openWindow") replaces the window.open function with a stub you can assert against. Instead of moving to a new tab that is hard to control, the test verifies that the application really calls open with the correct URL.

Drag and Drop, Uploads, and Keyboard Events

Drag and Drop

Drag and drop in the browser fires a sequence of events. The most reliable approach uses .trigger() to simulate the event sequence:

JSSimulating drag and drop
cy.get("[data-cy=item]").trigger("dragstart");
cy.get("[data-cy=dropzone]").trigger("drop");

cy.trigger("dragstart") and cy.trigger("drop") fire DOM events directly. For drag-and-drop libraries that need coordinates, combine .trigger("dragstart", { clientX: 0, clientY: 0 }) followed by .trigger("drop", { clientX: 200, clientY: 200 }).

Uploading Files

cy.selectFile lets you upload real files without extra plugins:

JSUploading a file
cy.get("[data-cy=input-avatar]").selectFile("cypress/fixtures/avatar.png");
cy.contains("Avatar terunggah").should("be.visible");

cy.selectFile("cypress/fixtures/avatar.png") attaches the file to the upload input and triggers the change event. Keep sample files in the fixtures folder so tests do not depend on a developer's files.

Keyboard Events

For keyboard actions such as shortcuts:

JSKeyboard shortcut
cy.get("[data-cy=editor]").type("{ctrl}s");
cy.contains("Tersimpan").should("be.visible");

cy.type("{ctrl}s") presses a key combination. Syntax like {enter}, {esc}, {backspace}, and {shift}tab is available for other needs.

Testing Single-Page Apps and Dynamic Content

SPAs (single-page apps) render content dynamically without a full page reload. This is an advantage: page state is relatively stable. What needs attention is elements that appear in alternation — spinners, skeletons, and swapped-out content.

JSWaiting for dynamic SPA content
cy.intercept("GET", "/api/produk").as("produk");
 
cy.get("[data-cy=tab-terbaru]").click();
cy.wait("@produk");
cy.get("[data-cy=list-produk]").children().should("have.length.gt", 0);

Here cy.wait("@produk") waits for the latest data to finish loading before checking the list. Waiting on network events is the most deterministic way to handle dynamic content — far better than guessing animation durations.

Network Stubbing with cy.intercept

cy.intercept is the most powerful tool for dynamic content: you can take full control of network responses:

JSStubbing and controlling responses
cy.intercept("GET", "/api/produk/*", (req) => {
  req.reply({ statusCode: 500, body: { error: "server down" } });
}).as("produkGagal");
 
cy.get("[data-cy=muat-produk]").click();
cy.contains("Gagal memuat produk").should("be.visible");

req.reply({ statusCode: 500, body: ... }) forces the application to receive a server failure. Scenarios like retry and fallback flows can be tested without taking down a real server — we will dig deeper into this in episode 12.

Tip

Set up cy.intercept in beforeEach so all tests in one describe are consistent. Make sure the URL pattern is specific — an overly broad * can catch unwanted requests and cause tests to interfere with one another.

Closing

Episode 8 expanded the range of interactions: cross-origin iframes with cy.origin(), popups and new windows by stubbing window.open, drag and drop with trigger, uploads with cy.selectFile, keyboard events with cy.type, SPA dynamic content with cy.wait("@alias"), and full network control with cy.intercept.

Key takeaways:

  • cy.origin() unlocks testing on different domains.
  • Stub window.open so popups can be asserted without switching tabs.
  • cy.selectFile handles real file uploads.
  • cy.type("{ctrl}s") mimics keyboard shortcuts.
  • Wait for dynamic content via network events, not by guessing timing.

In the next episode, episode 9, we will cover component testing — testing React, Vue, and Angular components in isolation, setting up the component environment, behavior-based component assertions, and Storybook integration. Your tests start moving down one level from full pages to components.