cypress-labs

Cypress Playground — 50+ Interactive Practice Labs | QA CSEIAN
Cypress Playground

50+ real interactive Cypress labs.

The same 50+ widgets from the Playwright playground, but with Cypress-native code (cy.intercept, cy.session, selectFile, includeShadowDom). Mirror labs let you compare frameworks side-by-side and pick the right tool for each project.

52 labs
easy Cypress

Dynamic Locators

Locate elements whose IDs change on every page load using resilient Playwright locators like [id^="submit-"] regex matches.

#01
Live widget (shared with Playwright)
Challenge: Find and click the button even though its ID changes from #submit-a3f to #submit-7b2 every refresh.
Expected: Button clicked, success message appears.
Interactive area
JavaScript TypeScript
describe('Dynamic Locators', () => {
  it('clicks button with dynamic id', () => {
    cy.visit('/p/playwright-labs.html#dynamic-locators');
    class=class="s">"c">// Use data-cy or attribute selector — survives id changes
    cy.get('button[id^="submit-"]').click();
    cy.get('#result-dynamic-id').should('have.text', 'Clicked!');
  });
});
describe('Dynamic Locators', () => {
  it('clicks button with dynamic id', () => {
    cy.visit('/p/playwright-labs.html#dynamic-locators');
    cy.get('button[id^="submit-"]').click();
    cy.get('#result-dynamic-id').should('have.text', 'Clicked!');
  });
});
easy Cypress

Auto Waiting

Playwright auto-waits for elements to be visible, enabled, and stable before interacting — no more flaky page.waitForTimeout() calls.

#02
Live widget (shared with Playwright)
Challenge: Click a button that appears 2 seconds after page load. Do not use any sleep.
Expected: Test passes without any manual waitForTimeout.
Interactive area
Button appears 2s after page load.
JavaScript TypeScript
describe('Auto Waiting', () => {
  it('clicks delayed button without cy.wait()', () => {
    cy.visit('/p/playwright-labs.html#auto-waiting');
    class=class="s">"c">// Cypress auto-retries — no cy.wait() needed
    cy.get('#delayed-button').click({ timeout: 10000 });
    cy.get('#result-auto-wait').should('have.text', 'Loaded!');
  });
});
describe('Auto Waiting', () => {
  it('clicks delayed button without cy.wait()', () => {
    cy.visit('/p/playwright-labs.html#auto-waiting');
    cy.get('#delayed-button').click({ timeout: 10000 });
    cy.get('#result-auto-wait').should('have.text', 'Loaded!');
  });
});
medium Cypress

Frames (iFrame)

Use Playwright's frameLocator() to interact with elements inside same-origin iframes. Modern alternative to legacy frame(name) API.

#03
Live widget (shared with Playwright)
Challenge: Click the button inside the iframe below and verify the result text appears.
Expected: Click reaches the iframe button, result updates.
Interactive area
JavaScript TypeScript
describe('Frames (iFrame)', () => {
  it('clicks button inside iframe', () => {
    cy.visit('/p/playwright-labs.html#frames');
    cy.frameLoaded('#lab-iframe');
    cy.iframe('#lab-iframe').find('#iframe-button').click();
    cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Iframe clicked!');
  });
});
class=class="s">"c">// Requires cypress-iframe plugin: npm i -D cypress-iframe
class=class="s">"c">// In cypress/support/e2e.js: import 'cypress-iframe';
describe('Frames (iFrame)', () => {
  it('clicks button inside iframe', () => {
    cy.visit('/p/playwright-labs.html#frames');
    cy.frameLoaded('#lab-iframe');
    cy.iframe('#lab-iframe').find('#iframe-button').click();
    cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Iframe clicked!');
  });
});
class=class="s">"c">// Requires cypress-iframe plugin: npm i -D cypress-iframe
class=class="s">"c">// In cypress/support/e2e.js: import 'cypress-iframe';
medium Cypress

Nested Frames

Chain frameLocator() calls to drill into frames within frames — the modern alternative to traversing window.frames.

#04
Live widget (shared with Playwright)
Challenge: Click the button inside the innermost iframe (an iframe inside an iframe).
Expected: Click reaches the inner frame's button.
Interactive area
JavaScript TypeScript
describe('Nested Frames', () => {
  it('clicks button in nested iframe', () => {
    cy.visit('/p/playwright-labs.html#nested-frames');
    class=class="s">"c">// Cypress requires entering each iframe level
    cy.frameLoaded('#outer-frame');
    cy.iframe('#outer-frame').find('#inner-frame').should('exist');
    class=class="s">"c">// For deeper nesting, use the cypress-iframe plugin's iframe()
    cy.iframe('#outer-frame').within(() => {
      cy.iframe('#inner-frame').find('#nested-button').click();
    });
  });
});
describe('Nested Frames', () => {
  it('clicks button in nested iframe', () => {
    cy.visit('/p/playwright-labs.html#nested-frames');
    cy.frameLoaded('#outer-frame');
    cy.iframe('#outer-frame').find('#inner-frame').should('exist');
    cy.iframe('#outer-frame').within(() => {
      cy.iframe('#inner-frame').find('#nested-button').click();
    });
  });
});
medium Cypress

Drag and Drop

Use dragTo() for simple HTML5 drag and drop, or manual mouse.down()/move()/up() for pixel-perfect scenarios.

#05
Live widget (shared with Playwright)
Challenge: Drag Card A from the To-Do column into the Done column.
Expected: Card A appears in the Done column after the drop.
Interactive area
To-Do
Card A
Card B
Done
JavaScript TypeScript
describe('Drag and Drop', () => {
  it('drags card to done column', () => {
    cy.visit('/p/playwright-labs.html#drag-and-drop');
    class=class="s">"c">// Requires @4tw/cypress-drag-drop plugin
    cy.get('[data-testid="card-a"]').drag('[data-testid="col-done"]');
    cy.get('[data-testid="col-done"]').find('[data-testid="card-a"]').should('exist');
  });
});
class=class="s">"c">// npm i -D @4tw/cypress-drag-drop
class=class="s">"c">// In cypress/support/e2e.js: require('@4tw/cypress-drag-drop');
describe('Drag and Drop', () => {
  it('drags card to done column', () => {
    cy.visit('/p/playwright-labs.html#drag-and-drop');
    cy.get('[data-testid="card-a"]').drag('[data-testid="col-done"]');
    cy.get('[data-testid="col-done"]').find('[data-testid="card-a"]').should('exist');
  });
});
medium Cypress

Alert Dialogs

Auto-dismiss native window.alert() dialogs by registering a page.on('dialog') handler before triggering the action.

#06
Live widget (shared with Playwright)
Challenge: Click the button that fires alert('Hello!') and verify the alert was accepted.
Expected: Alert auto-accepted, status text appears.
Interactive area
JavaScript TypeScript
describe('Alert Dialogs', () => {
  it('auto-accepts alert', () => {
    const stub = cy.stub();
    cy.on('window:alert', stub);
    cy.visit('/p/playwright-labs.html#alerts');
    cy.get('#alert-button').click()
      .then(() => expect(stub).to.be.calledWith('Hello from QA CSEIAN!'));
    cy.get('#result-alert').should('have.text', 'Alert accepted');
  });
});
describe('Alert Dialogs', () => {
  it('auto-accepts alert', () => {
    const stub = cy.stub();
    cy.on('window:alert', stub);
    cy.visit('/p/playwright-labs.html#alerts');
    cy.get('#alert-button').click()
      .then(() => expect(stub).to.be.calledWith('Hello from QA CSEIAN!'));
    cy.get('#result-alert').should('have.text', 'Alert accepted');
  });
});
medium Cypress

Confirm Dialogs

Accept or dismiss window.confirm() dialogs programmatically by passing true/false to dialog.accept().

#07
Live widget (shared with Playwright)
Challenge: Accept the confirm() dialog and verify the UI reflects the acceptance.
Expected: Dialog accepted, status shows 'Confirmed'.
Interactive area
JavaScript TypeScript
describe('Confirm Dialogs', () => {
  it('accepts confirm dialog', () => {
    cy.visit('/p/playwright-labs.html#confirm-dialogs');
    cy.on('window:confirm', () => true);
    cy.get('#confirm-button').click();
    cy.get('#result-confirm').should('have.text', 'Confirmed');
  });
});
describe('Confirm Dialogs', () => {
  it('accepts confirm dialog', () => {
    cy.visit('/p/playwright-labs.html#confirm-dialogs');
    cy.on('window:confirm', () => true);
    cy.get('#confirm-button').click();
    cy.get('#result-confirm').should('have.text', 'Confirmed');
  });
});
medium Cypress

Prompt Dialogs

Provide text input to window.prompt() dialogs by passing the text to dialog.accept('text').

#08
Live widget (shared with Playwright)
Challenge: Type 'Playwright' into the prompt and verify the page echoes it back.
Expected: Echo shows the typed name.
Interactive area
JavaScript TypeScript
describe('Prompt Dialogs', () => {
  it('provides text to prompt', () => {
    cy.visit('/p/playwright-labs.html#prompt-dialogs');
    cy.window().then(win => cy.stub(win, 'prompt').returns('Cypress'));
    cy.get('#prompt-button').click();
    cy.get('#result-prompt').should('have.text', 'Hello, Cypress!');
  });
});
describe('Prompt Dialogs', () => {
  it('provides text to prompt', () => {
    cy.visit('/p/playwright-labs.html#prompt-dialogs');
    cy.window().then((win) => cy.stub(win, 'prompt').returns('Cypress'));
    cy.get('#prompt-button').click();
    cy.get('#result-prompt').should('have.text', 'Hello, Cypress!');
  });
});
medium Cypress

File Upload

Upload files via setInputFiles() — works even on hidden inputs without triggering the OS file picker.

#09
Live widget (shared with Playwright)
Challenge: Upload a file to the hidden input and verify the filename appears.
Expected: Selected filename shown in the result.
Interactive area
JavaScript TypeScript
describe('File Upload', () => {
  it('uploads file via selectFile', () => {
    cy.visit('/p/playwright-labs.html#file-upload');
    cy.get('#file-input').selectFile({
      contents: Cypress.Buffer.from('id,name\\n1,QACSEIAN'),
      fileName: 'sample.csv',
      mimeType: 'text/csv'
    });
    cy.get('#result-upload').should('contain', 'sample.csv');
  });
});
describe('File Upload', () => {
  it('uploads file via selectFile', () => {
    cy.visit('/p/playwright-labs.html#file-upload');
    cy.get('#file-input').selectFile({
      contents: Cypress.Buffer.from('id,name\\n1,QACSEIAN'),
      fileName: 'sample.csv',
      mimeType: 'text/csv'
    });
    cy.get('#result-upload').should('contain', 'sample.csv');
  });
});
medium Cypress

Multiple File Upload

Upload multiple files in a single setInputFiles() call by passing an array.

#10
Live widget (shared with Playwright)
Challenge: Upload two files and verify both filenames appear in the result.
Expected: Both file names listed in the result.
Interactive area
JavaScript TypeScript
describe('Multiple File Upload', () => {
  it('uploads multiple files', () => {
    cy.visit('/p/playwright-labs.html#multiple-file-upload');
    cy.get('#multi-file-input').selectFile([
      { contents: Cypress.Buffer.from('aaa'), fileName: 'a.txt' },
      { contents: Cypress.Buffer.from('bbb'), fileName: 'b.txt' }
    ]);
    cy.get('#result-multi-upload').should('contain', 'a.txt').and('contain', 'b.txt');
  });
});
describe('Multiple File Upload', () => {
  it('uploads multiple files', () => {
    cy.visit('/p/playwright-labs.html#multiple-file-upload');
    cy.get('#multi-file-input').selectFile([
      { contents: Cypress.Buffer.from('aaa'), fileName: 'a.txt' },
      { contents: Cypress.Buffer.from('bbb'), fileName: 'b.txt' }
    ]);
    cy.get('#result-multi-upload').should('contain', 'a.txt').and('contain', 'b.txt');
  });
});
medium Cypress

File Download

Capture file downloads via the download event and save them to disk with download.saveAs().

#11
Live widget (shared with Playwright)
Challenge: Click the download button, capture the file, and verify its contents.
Expected: Download captured and saved successfully.
Interactive area
Click to download qa-cseian-report.txt
JavaScript TypeScript
describe('File Download', () => {
  it('captures downloaded file', () => {
    cy.visit('/p/playwright-labs.html#file-download');
    cy.get('#download-button').click();
    class=class="s">"c">// Cypress 12+ reads downloads from cypress/downloads/
    cy.readFile('cypress/downloads/qa-cseian-report.txt').should('contain', 'QA CSEIAN');
  });
});
describe('File Download', () => {
  it('captures downloaded file', () => {
    cy.visit('/p/playwright-labs.html#file-download');
    cy.get('#download-button').click();
    cy.readFile('cypress/downloads/qa-cseian-report.txt').should('contain', 'QA CSEIAN');
  });
});
medium Cypress

New Window

Capture new windows opened via window.open() using page.waitForEvent('popup').

#12
Live widget (shared with Playwright)
Challenge: Click a button that opens a new window and verify the popup's URL.
Expected: Popup captured and asserted.
Interactive area
Open via link ↗
Popups open about:blank — replace URL in your test.
JavaScript TypeScript
describe('New Window', () => {
  it('captures popup window', () => {
    cy.visit('/p/playwright-labs.html#new-window');
    class=class="s">"c">// Cypress doesn't support multi-tab natively — remove target
    cy.window().then(win => {
      cy.stub(win, 'open').callsFake(url => {
        win.location.href = url;
      });
    });
    cy.get('#popup-button').click();
  });
});
describe('New Window', () => {
  it('captures popup window', () => {
    cy.visit('/p/playwright-labs.html#new-window');
    cy.window().then((win) => {
      cy.stub(win, 'open').callsFake((url) => {
        win.location.href = url;
      });
    });
    cy.get('#popup-button').click();
  });
});
medium Cypress

Multiple Tabs

Same as new window — Playwright treats every browser tab as a new Page context. Use page.context().pages to enumerate.

#13
Live widget (shared with Playwright)
Challenge: Open two tabs and verify both are accessible from page.context().pages.
Expected: Both pages exist in the context.
Interactive area
Open via link ↗
Popups open about:blank — replace URL in your test.
JavaScript TypeScript
describe('Multiple Tabs', () => {
  it('opens link in same tab', () => {
    cy.visit('/p/playwright-labs.html#multiple-tabs');
    class=class="s">"c">// Cypress single-tab: remove target=_blank
    cy.get('#popup-link').invoke('removeAttr', 'target').click();
    cy.url().should('include', 'about:blank');
  });
});
describe('Multiple Tabs', () => {
  it('opens link in same tab', () => {
    cy.visit('/p/playwright-labs.html#multiple-tabs');
    cy.get('#popup-link').invoke('removeAttr', 'target').click();
    cy.url().should('include', 'about:blank');
  });
});
easy Cypress

Hover

Trigger CSS :hover states reliably with locator.hover() — useful for tooltips, dropdown menus, and revealing hidden elements.

#14
Live widget (shared with Playwright)
Challenge: Hover over the card to reveal the tooltip and verify the tooltip text.
Expected: Tooltip appears on hover.
Interactive area
Hover me
JavaScript TypeScript
describe('Hover', () => {
  it('reveals tooltip on hover', () => {
    cy.visit('/p/playwright-labs.html#hover');
    cy.get('#hover-target').trigger('mouseover');
    cy.get('#hover-tooltip').should('be.visible').and('have.text', 'You hovered!');
  });
});
describe('Hover', () => {
  it('reveals tooltip on hover', () => {
    cy.visit('/p/playwright-labs.html#hover');
    cy.get('#hover-target').trigger('mouseover');
    cy.get('#hover-tooltip').should('be.visible').and('have.text', 'You hovered!');
  });
});
medium Cypress

Keyboard Actions

Use page.keyboard to type text, press keys, and trigger shortcuts like Ctrl+Enter or Shift+Tab.

#15
Live widget (shared with Playwright)
Challenge: Type 'QA' into the input, press Enter, and verify the echoed text.
Expected: Input captures the typed text and Enter submits it.
Interactive area
JavaScript TypeScript
describe('Keyboard Actions', () => {
  it('types and presses Enter', () => {
    cy.visit('/p/playwright-labs.html#keyboard-actions');
    cy.get('#kbd-input').type('QA CSEIAN{enter}');
    cy.get('#result-keyboard').should('have.text', 'Submitted: QA CSEIAN');
  });
});
describe('Keyboard Actions', () => {
  it('types and presses Enter', () => {
    cy.visit('/p/playwright-labs.html#keyboard-actions');
    cy.get('#kbd-input').type('QA CSEIAN{enter}');
    cy.get('#result-keyboard').should('have.text', 'Submitted: QA CSEIAN');
  });
});
medium Cypress

Mouse Actions

Use page.mouse for pixel-perfect interactions — down(), move(), up(), and wheel() for scroll.

#16
Live widget (shared with Playwright)
Challenge: Click on the canvas at coordinates (50, 50) and verify the click was registered.
Expected: Click coordinates captured and displayed.
Interactive area
Click anywhere in here
JavaScript TypeScript
describe('Mouse Actions', () => {
  it('clicks at coordinates', () => {
    cy.visit('/p/playwright-labs.html#mouse-actions');
    cy.get('#mouse-area').click(50, 50);
    cy.get('#result-mouse').should('contain', '50,50');
  });
});
describe('Mouse Actions', () => {
  it('clicks at coordinates', () => {
    cy.visit('/p/playwright-labs.html#mouse-actions');
    cy.get('#mouse-area').click(50, 50);
    cy.get('#result-mouse').should('contain', '50,50');
  });
});
hard Cypress

Shadow DOM

Playwright pierces open AND closed shadow roots by default — no special CSS combinators needed.

#17
Live widget (shared with Playwright)
Challenge: Click the button inside the closed shadow DOM below.
Expected: Click reaches the shadow button, no ElementNotFound.
Interactive area
JavaScript TypeScript
describe('Shadow DOM', () => {
  it('clicks button in shadow root', () => {
    cy.visit('/p/playwright-labs.html#shadow-dom');
    class=class="s">"c">// Use includeShadowDom option (Cypress 10+)
    cy.get('button', { includeShadowDom: true }).contains('Activate').click();
    cy.get('#shadow-host').should('contain', 'Shadow clicked!');
  });
});
describe('Shadow DOM', () => {
  it('clicks button in shadow root', () => {
    cy.visit('/p/playwright-labs.html#shadow-dom');
    cy.get('button', { includeShadowDom: true }).contains('Activate').click();
    cy.get('#shadow-host').should('contain', 'Shadow clicked!');
  });
});
medium Cypress

Infinite Scroll

Use page.mouse.wheel() or scrollIntoViewIfNeeded() to load additional content as the user scrolls.

#18
Live widget (shared with Playwright)
Challenge: Scroll the container until at least 20 items are loaded.
Expected: Item count grows as you scroll.
Interactive area
    JavaScript TypeScript
    describe('Infinite Scroll', () => {
      it('loads more items on scroll', () => {
        cy.visit('/p/playwright-labs.html#infinite-scroll');
        cy.get('#scroll-list li').should('have.length', 5);
        cy.get('#scroll-list').scrollTo('bottom');
        cy.get('#scroll-list li').should('have.length.at.least', 10);
      });
    });
    describe('Infinite Scroll', () => {
      it('loads more items on scroll', () => {
        cy.visit('/p/playwright-labs.html#infinite-scroll');
        cy.get('#scroll-list li').should('have.length', 5);
        cy.get('#scroll-list').scrollTo('bottom');
        cy.get('#scroll-list li').should('have.length.at.least', 10);
      });
    });
    medium Cypress

    Lazy Loading

    Wait for IntersectionObserver-triggered image loads using Playwright's network idle or img loading states.

    #19
    Live widget (shared with Playwright)
    Challenge: Scroll to trigger lazy loading and verify all 6 images eventually load.
    Expected: All images have non-empty naturalWidth.
    Interactive area
    lazy 1 lazy 2 lazy 3 lazy 4 lazy 5 lazy 6
    JavaScript TypeScript
    describe('Lazy Loading', () => {
      it('loads images on scroll', () => {
        cy.visit('/p/playwright-labs.html#lazy-loading');
        cy.get('#lazy-grid img').each($img => {
          cy.wrap($img).scrollIntoView().should($el => {
            expect($el[0].naturalWidth).to.be.greaterThan(0);
          });
        });
      });
    });
    describe('Lazy Loading', () => {
      it('loads images on scroll', () => {
        cy.visit('/p/playwright-labs.html#lazy-loading');
        cy.get('#lazy-grid img').each(($img) => {
          cy.wrap($img).scrollIntoView().should(($el) => {
            expect($el[0].naturalWidth).to.be.greaterThan(0);
          });
        });
      });
    });
    hard Cypress

    Network Interception

    Use page.route() to intercept, modify, or fully mock XHR and fetch responses in your tests.

    #20
    Live widget (shared with Playwright)
    Challenge: Mock the API to return a custom response and verify the UI displays it.
    Expected: UI shows mocked data instead of real response.
    Interactive area
    Calls /api/lab-data — intercept with cy.intercept()
    JavaScript TypeScript
    describe('Network Interception', () => {
      it('mocks API response with cy.intercept', () => {
        cy.intercept('GET', '/api/lab-data', {
          statusCode: 200,
          body: { items: ['Mocked A', 'Mocked B'] }
        }).as('labData');
        cy.visit('/p/playwright-labs.html#network-interception');
        cy.get('#fetch-button').click();
        cy.wait('@labData');
        cy.get('#result-network').should('contain', 'Mocked A').and('contain', 'Mocked B');
      });
    });
    describe('Network Interception', () => {
      it('mocks API response with cy.intercept', () => {
        cy.intercept('GET', '/api/lab-data', {
          statusCode: 200,
          body: { items: ['Mocked A', 'Mocked B'] }
        }).as('labData');
        cy.visit('/p/playwright-labs.html#network-interception');
        cy.get('#fetch-button').click();
        cy.wait('@labData');
        cy.get('#result-network').should('contain', 'Mocked A').and('contain', 'Mocked B');
      });
    });
    hard Cypress

    API Mocking

    Same as network interception — stub any HTTP call with computed responses, fixtures, or status codes.

    #21
    Live widget (shared with Playwright)
    Challenge: Mock a 500 error on the API and verify the error UI renders.
    Expected: Error UI appears without real request.
    Interactive area
    Calls /api/lab-data — intercept with cy.intercept()
    JavaScript TypeScript
    describe('API Mocking', () => {
      it('mocks 500 error', () => {
        cy.intercept('GET', '/api/lab-data', {
          statusCode: 500,
          body: 'Server error'
        }).as('errApi');
        cy.visit('/p/playwright-labs.html#api-mocking');
        cy.get('#fetch-button').click();
        cy.get('#result-network').should('have.text', 'Error: 500');
      });
    });
    describe('API Mocking', () => {
      it('mocks 500 error', () => {
        cy.intercept('GET', '/api/lab-data', {
          statusCode: 500,
          body: 'Server error'
        }).as('errApi');
        cy.visit('/p/playwright-labs.html#api-mocking');
        cy.get('#fetch-button').click();
        cy.get('#result-network').should('have.text', 'Error: 500');
      });
    });
    hard Cypress

    Route Mocking

    Mock entire route trees including query strings and POST bodies — useful for testing state transitions without backend.

    #22
    Live widget (shared with Playwright)
    Challenge: Mock the route to return different data based on the page query parameter.
    Expected: Mocked route returns correct data per page.
    Interactive area
    Calls /api/lab-data — intercept with cy.intercept()
    JavaScript TypeScript
    describe('Route Mocking', () => {
      it('mocks route with query string', () => {
        cy.intercept('GET', '/api/lab-data**', (req) => {
          const page = new URL(req.url).searchParams.get('page') || '1';
          req.reply({
            statusCode: 200,
            body: { items: [class="s">`Item from page ${page}`] }
          });
        }).as('routeApi');
        cy.visit('/p/playwright-labs.html#route-mocking');
        cy.get('#fetch-button').click();
        cy.get('#result-network').should('contain', 'Item from page 1');
      });
    });
    describe('Route Mocking', () => {
      it('mocks route with query string', () => {
        cy.intercept('GET', '/api/lab-data**', (req) => {
          const page = new URL(req.url).searchParams.get('page') || '1';
          req.reply({
            statusCode: 200,
            body: { items: [class="s">`Item from page ${page}`] }
          });
        }).as('routeApi');
        cy.visit('/p/playwright-labs.html#route-mocking');
        cy.get('#fetch-button').click();
        cy.get('#result-network').should('contain', 'Item from page 1');
      });
    });
    hard Cypress

    Authentication

    Save authenticated storage state once with storageState() and reuse across every test for fast, parallel runs.

    #23
    Live widget (shared with Playwright)
    Challenge: Log in once, save storageState to auth.json, then reuse it for subsequent specs.
    Expected: Tests skip login and start authenticated.
    Interactive area
    JavaScript TypeScript
    class=class="s">"c">// cypress/e2e/auth.cy.js
    describe('Authentication with cy.session', () => {
      beforeEach(() => {
        cy.session('qa-user', () => {
          cy.visit('/p/playwright-labs.html#authentication');
          cy.get('#auth-username').type('qa@imcseian.com');
          cy.get('#auth-login').click();
          cy.get('#auth-status').should('contain', 'Logged in');
        });
      });
    
      it('preserves session across tests', () => {
        cy.visit('/p/playwright-labs.html#authentication');
        class=class="s">"c">// Already logged in — no need to log in again
      });
    });
    class=class="s">"c">// cypress/e2e/auth.cy.ts
    describe('Authentication with cy.session', () => {
      beforeEach(() => {
        cy.session('qa-user', () => {
          cy.visit('/p/playwright-labs.html#authentication');
          cy.get('#auth-username').type('qa@imcseian.com');
          cy.get('#auth-login').click();
          cy.get('#auth-status').should('contain', 'Logged in');
        });
      });
    
      it('preserves session across tests', () => {
        cy.visit('/p/playwright-labs.html#authentication');
      });
    });
    medium Cypress

    Local Storage

    Read, write, and clear localStorage entries pre-test using page.evaluate() or context.addInitScript().

    #24
    Live widget (shared with Playwright)
    Challenge: Set a localStorage key before navigation and verify the widget reads it.
    Expected: Pre-set value appears in the widget.
    Interactive area
    Current value: (none)
    JavaScript TypeScript
    describe('Local Storage', () => {
      it('pre-sets localStorage value', () => {
        cy.visit('/p/playwright-labs.html#local-storage', {
          onBeforeLoad(win) {
            win.localStorage.setItem('qa-cseian-theme', 'dark');
          }
        });
        cy.get('#storage-display').should('contain', 'qa-cseian-theme=dark');
      });
    });
    describe('Local Storage', () => {
      it('pre-sets localStorage value', () => {
        cy.visit('/p/playwright-labs.html#local-storage', {
          onBeforeLoad(win: Window) {
            win.localStorage.setItem('qa-cseian-theme', 'dark');
          }
        });
        cy.get('#storage-display').should('contain', 'qa-cseian-theme=dark');
      });
    });
    medium Cypress

    Session Storage

    Persist session-only keys between page reloads using sessionStorage — cleared when the tab closes.

    #25
    Live widget (shared with Playwright)
    Challenge: Set sessionStorage via Playwright and verify the widget picks it up.
    Expected: Session value displayed in widget.
    Interactive area
    Current value: (none)
    JavaScript TypeScript
    describe('Session Storage', () => {
      it('sets sessionStorage via window', () => {
        cy.visit('/p/playwright-labs.html#session-storage');
        cy.window().then(win => {
          win.sessionStorage.setItem('qa-cseian-session', 'abc123');
        });
        cy.reload();
        cy.get('#storage-display').should('contain', 'qa-cseian-session=abc123');
      });
    });
    describe('Session Storage', () => {
      it('sets sessionStorage via window', () => {
        cy.visit('/p/playwright-labs.html#session-storage');
        cy.window().then((win) => {
          win.sessionStorage.setItem('qa-cseian-session', 'abc123');
        });
        cy.reload();
        cy.get('#storage-display').should('contain', 'qa-cseian-session=abc123');
      });
    });
    medium Cypress

    Cookies

    Inspect, set, and clear cookies with context.addCookies() and context.cookies().

    #26
    Live widget (shared with Playwright)
    Challenge: Pre-set a cookie and verify the page reads it on load.
    Expected: Cookie value displayed in widget.
    Interactive area
    Cookie "qa-cseian-user": (not set)
    JavaScript TypeScript
    describe('Cookies', () => {
      it('pre-sets cookie via cy.setCookie', () => {
        cy.visit('/p/playwright-labs.html#cookies');
        cy.setCookie('qa-cseian-user', 'ram');
        cy.reload();
        cy.get('#cookie-display').should('have.text', 'ram');
      });
    });
    describe('Cookies', () => {
      it('pre-sets cookie via cy.setCookie', () => {
        cy.visit('/p/playwright-labs.html#cookies');
        cy.setCookie('qa-cseian-user', 'ram');
        cy.reload();
        cy.get('#cookie-display').should('have.text', 'ram');
      });
    });
    medium Cypress

    Web Tables

    Read and assert tabular data row-by-row using locator.locator('tr') and toContainText() assertions.

    #27
    Live widget (shared with Playwright)
    Challenge: Find the row containing 'Ram Pothuraju' and verify the role is 'Test Engineer'.
    Expected: Row found, role verified.
    Interactive area
    NameRoleEmail
    Ram PothurajuTest Engineerqa@imcseian.com
    Ananya RaoSDETananya@example.com
    Marcus KimQA Leadmarcus@example.com
    JavaScript TypeScript
    describe('Web Tables', () => {
      it('finds row by text', () => {
        cy.visit('/p/playwright-labs.html#web-tables');
        cy.get('#web-table tr').contains('Ram Pothuraju').parent('tr').within(() => {
          cy.get('td').eq(1).should('have.text', 'Test Engineer');
          cy.get('td').eq(2).should('have.text', 'qa@imcseian.com');
        });
      });
    });
    describe('Web Tables', () => {
      it('finds row by text', () => {
        cy.visit('/p/playwright-labs.html#web-tables');
        cy.get('#web-table tr').contains('Ram Pothuraju').parent('tr').within(() => {
          cy.get('td').eq(1).should('have.text', 'Test Engineer');
          cy.get('td').eq(2).should('have.text', 'qa@imcseian.com');
        });
      });
    });
    medium Cypress

    Dynamic Tables

    Handle tables whose rows appear after async fetches — wait for the row count to grow.

    #28
    Live widget (shared with Playwright)
    Challenge: Click 'Load' and wait for 5 rows to appear in the table.
    Expected: All 5 rows visible after load.
    Interactive area
    IDItemStatus
    JavaScript TypeScript
    describe('Dynamic Tables', () => {
      it('waits for table rows', () => {
        cy.visit('/p/playwright-labs.html#dynamic-tables');
        cy.get('#load-table').click();
        cy.get('#dynamic-table tbody tr').should('have.length', 5);
      });
    });
    describe('Dynamic Tables', () => {
      it('waits for table rows', () => {
        cy.visit('/p/playwright-labs.html#dynamic-tables');
        cy.get('#load-table').click();
        cy.get('#dynamic-table tbody tr').should('have.length', 5);
      });
    });
    medium Cypress

    Pagination

    Walk through numbered pagination by clicking Next/Prev and verifying the active page changes.

    #29
    Live widget (shared with Playwright)
    Challenge: Navigate from page 1 to page 3 and verify the page indicator updates.
    Expected: Page indicator shows 'Page 3 of 3'.
    Interactive area
    Page 1 of 3
    JavaScript TypeScript
    describe('Pagination', () => {
      it('navigates to last page', () => {
        cy.visit('/p/playwright-labs.html#pagination');
        cy.get('#page-next').click().click();
        cy.get('#page-indicator').should('have.text', 'Page 3 of 3');
      });
    });
    describe('Pagination', () => {
      it('navigates to last page', () => {
        cy.visit('/p/playwright-labs.html#pagination');
        cy.get('#page-next').click().click();
        cy.get('#page-indicator').should('have.text', 'Page 3 of 3');
      });
    });
    medium Cypress

    Calendar

    Navigate custom calendar widgets by clicking next/prev and selecting the day cell.

    #30
    Live widget (shared with Playwright)
    Challenge: Open the calendar, navigate to next month, and pick the 15th.
    Expected: Selected date shows in the result.
    Interactive area
    JavaScript TypeScript
    describe('Calendar', () => {
      it('picks date from calendar', () => {
        cy.visit('/p/playwright-labs.html#calendar');
        cy.get('#cal-open').click();
        cy.get('#cal-next').click();
        cy.get('#cal-grid [data-day="15"]').click();
        cy.get('#result-calendar').should('contain', '15');
      });
    });
    describe('Calendar', () => {
      it('picks date from calendar', () => {
        cy.visit('/p/playwright-labs.html#calendar');
        cy.get('#cal-open').click();
        cy.get('#cal-next').click();
        cy.get('#cal-grid [data-day="15"]').click();
        cy.get('#result-calendar').should('contain', '15');
      });
    });
    medium Cypress

    Date Picker

    Fill native date inputs with ISO-format strings (YYYY-MM-DD) for reliable cross-browser behavior.

    #31
    Live widget (shared with Playwright)
    Challenge: Set the date input to 2025-12-25 and verify the displayed value.
    Expected: Date input shows the chosen date.
    Interactive area
    JavaScript TypeScript
    describe('Date Picker', () => {
      it('fills date input', () => {
        cy.visit('/p/playwright-labs.html#date-picker');
        cy.get('#date-input').type('2025-12-25');
        cy.get('#date-input').should('have.value', '2025-12-25');
        cy.get('#result-date').should('have.text', '2025-12-25');
      });
    });
    describe('Date Picker', () => {
      it('fills date input', () => {
        cy.visit('/p/playwright-labs.html#date-picker');
        cy.get('#date-input').type('2025-12-25');
        cy.get('#date-input').should('have.value', '2025-12-25');
        cy.get('#result-date').should('have.text', '2025-12-25');
      });
    });
    medium Cypress

    Slider

    Set the value of native range sliders via fill() or simulate drag with mouse events.

    #32
    Live widget (shared with Playwright)
    Challenge: Set the slider to 75 and verify the displayed value.
    Expected: Slider value shows 75.
    Interactive area
    50
    JavaScript TypeScript
    describe('Slider', () => {
      it('sets slider value', () => {
        cy.visit('/p/playwright-labs.html#slider');
        cy.get('#range-slider').invoke('val', 75).trigger('input');
        cy.get('#result-slider').should('have.text', '75');
      });
    });
    describe('Slider', () => {
      it('sets slider value', () => {
        cy.visit('/p/playwright-labs.html#slider');
        cy.get('#range-slider').invoke('val', 75).trigger('input');
        cy.get('#result-slider').should('have.text', '75');
      });
    });
    medium Cypress

    Multi Select

    Choose multiple options in select[multiple] by passing an array to selectOption().

    #34
    Live widget (shared with Playwright)
    Challenge: Select Playwright and Cypress together and verify both appear.
    Expected: Result shows both tools.
    Interactive area
    JavaScript TypeScript
    describe('Multi Select', () => {
      it('selects multiple options', () => {
        cy.visit('/p/playwright-labs.html#multi-select');
        cy.get('#multi-select').select(['playwright', 'cypress']);
        cy.get('#result-multi-select').should('contain', 'playwright').and('contain', 'cypress');
      });
    });
    describe('Multi Select', () => {
      it('selects multiple options', () => {
        cy.visit('/p/playwright-labs.html#multi-select');
        cy.get('#multi-select').select(['playwright', 'cypress']);
        cy.get('#result-multi-select').should('contain', 'playwright').and('contain', 'cypress');
      });
    });
    hard Cypress

    SVG

    Click SVG paths and assert on attribute changes — Playwright treats SVG elements like any other.

    #35
    Live widget (shared with Playwright)
    Challenge: Click the SVG circle and verify the result text changes.
    Expected: Click registers on the SVG element.
    Interactive area
    JavaScript TypeScript
    describe('SVG', () => {
      it('clicks SVG circle', () => {
        cy.visit('/p/playwright-labs.html#svg');
        cy.get('#svg-circle').click();
        cy.get('#result-svg').should('have.text', 'SVG clicked!');
      });
    });
    describe('SVG', () => {
      it('clicks SVG circle', () => {
        cy.visit('/p/playwright-labs.html#svg');
        cy.get('#svg-circle').click();
        cy.get('#result-svg').should('have.text', 'SVG clicked!');
      });
    });
    hard Cypress

    Canvas

    Interact with canvas pixels by clicking at specific coordinates — use boundingBox() to compute positions.

    #36
    Live widget (shared with Playwright)
    Challenge: Click on the canvas at its center and verify a dot appears.
    Expected: Canvas receives the click, count increments.
    Interactive area
    Clicks: 0
    JavaScript TypeScript
    describe('Canvas', () => {
      it('clicks canvas at center', () => {
        cy.visit('/p/playwright-labs.html#canvas');
        cy.get('#lab-canvas').click('center');
        cy.get('#result-canvas').should('contain', 'Clicks: 1');
      });
    });
    describe('Canvas', () => {
      it('clicks canvas at center', () => {
        cy.visit('/p/playwright-labs.html#canvas');
        cy.get('#lab-canvas').click('center');
        cy.get('#result-canvas').should('contain', 'Clicks: 1');
      });
    });
    hard Cypress

    Charts

    Assert on chart series data via data attributes or aria-labels — most modern chart libraries expose these.

    #37
    Live widget (shared with Playwright)
    Challenge: Verify the chart has 5 bars and the third bar has data-value='30'.
    Expected: Bar count and value verified.
    Interactive area
    JavaScript TypeScript
    describe('Charts', () => {
      it('asserts on chart bars', () => {
        cy.visit('/p/playwright-labs.html#charts');
        cy.get('#chart-bars [role="img"]').should('have.length', 5);
        cy.get('#chart-bars [role="img"]').eq(2).should('have.attr', 'data-value', '30');
      });
    });
    describe('Charts', () => {
      it('asserts on chart bars', () => {
        cy.visit('/p/playwright-labs.html#charts');
        cy.get('#chart-bars [role="img"]').should('have.length', 5);
        cy.get('#chart-bars [role="img"]').eq(2).should('have.attr', 'data-value', '30');
      });
    });
    medium Cypress

    Dynamic Forms

    Fill forms whose fields appear based on previous inputs — wait for the conditional field before filling.

    #38
    Live widget (shared with Playwright)
    Challenge: Select 'Other' from the role dropdown and verify the 'Specify' text field appears.
    Expected: Conditional field appears after selecting 'Other'.
    Interactive area
    JavaScript TypeScript
    describe('Dynamic Forms', () => {
      it('reveals conditional field', () => {
        cy.visit('/p/playwright-labs.html#dynamic-forms');
        cy.get('#role-select').select('other');
        cy.get('#specify-field').should('be.visible').type('SDET');
        cy.get('#result-form').should('have.text', 'SDET');
      });
    });
    describe('Dynamic Forms', () => {
      it('reveals conditional field', () => {
        cy.visit('/p/playwright-labs.html#dynamic-forms');
        cy.get('#role-select').select('other');
        cy.get('#specify-field').should('be.visible').type('SDET');
        cy.get('#result-form').should('have.text', 'SDET');
      });
    });
    medium Cypress

    Auto Complete

    Trigger autocomplete suggestions by typing, then click the desired option from the dropdown.

    #39
    Live widget (shared with Playwright)
    Challenge: Type 'play' and select 'Playwright' from the suggestions.
    Expected: Input value updates to 'Playwright'.
    Interactive area
    JavaScript TypeScript
    describe('Auto Complete', () => {
      it('selects from suggestions', () => {
        cy.visit('/p/playwright-labs.html#auto-complete');
        cy.get('#auto-input').type('play');
        cy.get('#auto-suggestions').should('be.visible');
        cy.get('#auto-suggestions [data-value="Playwright"]').click();
        cy.get('#auto-input').should('have.value', 'Playwright');
      });
    });
    describe('Auto Complete', () => {
      it('selects from suggestions', () => {
        cy.visit('/p/playwright-labs.html#auto-complete');
        cy.get('#auto-input').type('play');
        cy.get('#auto-suggestions').should('be.visible');
        cy.get('#auto-suggestions [data-value="Playwright"]').click();
        cy.get('#auto-input').should('have.value', 'Playwright');
      });
    });
    easy Cypress

    Accordions

    Expand and collapse accordion sections by clicking headers and asserting on the body visibility.

    #40
    Live widget (shared with Playwright)
    Challenge: Click the second accordion header and verify its body content is visible.
    Expected: Second accordion body visible with correct text.
    Interactive area
    Playwright +
    Playwright content here.
    Cypress +
    Cypress content here.
    Selenium +
    Selenium content here.
    JavaScript TypeScript
    describe('Accordions', () => {
      it('expands second accordion', () => {
        cy.visit('/p/playwright-labs.html#accordions');
        cy.get('#acc-header-2').click();
        cy.get('#acc-body-2').should('be.visible').and('have.text', 'Cypress content here.');
      });
    });
    describe('Accordions', () => {
      it('expands second accordion', () => {
        cy.visit('/p/playwright-labs.html#accordions');
        cy.get('#acc-header-2').click();
        cy.get('#acc-body-2').should('be.visible').and('have.text', 'Cypress content here.');
      });
    });
    medium Cypress

    Tree View

    Expand tree nodes by clicking the expand icon and assert on nested children appearing.

    #41
    Live widget (shared with Playwright)
    Challenge: Expand 'Automation' node and verify 'Playwright' child appears.
    Expected: Child node visible after expand.
    Interactive area
    Automation
    Playwright
    Cypress
    Manual Testing
    Exploratory
    JavaScript TypeScript
    describe('Tree View', () => {
      it('expands tree node', () => {
        cy.visit('/p/playwright-labs.html#tree-view');
        cy.get('#tree-node-automation .expand-btn').click();
        cy.get('#tree-node-playwright').should('be.visible');
      });
    });
    describe('Tree View', () => {
      it('expands tree node', () => {
        cy.visit('/p/playwright-labs.html#tree-view');
        cy.get('#tree-node-automation .expand-btn').click();
        cy.get('#tree-node-playwright').should('be.visible');
      });
    });
    medium Cypress

    Toast Messages

    Wait for transient toast notifications using toBeVisible() — they auto-dismiss after a timeout.

    #43
    Live widget (shared with Playwright)
    Challenge: Click the button and verify the toast appears with the correct text.
    Expected: Toast appears with 'Saved successfully'.
    Interactive area
    Saved successfully
    JavaScript TypeScript
    describe('Toast Messages', () => {
      it('waits for toast', () => {
        cy.visit('/p/playwright-labs.html#toast-messages');
        cy.get('#toast-button').click();
        cy.get('#lab-toast').should('be.visible').and('have.text', 'Saved successfully');
      });
    });
    describe('Toast Messages', () => {
      it('waits for toast', () => {
        cy.visit('/p/playwright-labs.html#toast-messages');
        cy.get('#toast-button').click();
        cy.get('#lab-toast').should('be.visible').and('have.text', 'Saved successfully');
      });
    });
    easy Cypress

    Loading Spinner

    Wait for spinners to disappear before the next action using toBeHidden().

    #44
    Live widget (shared with Playwright)
    Challenge: Click 'Submit' and wait for the spinner to disappear, then verify the success message.
    Expected: Spinner hides, success message appears.
    Interactive area
    JavaScript TypeScript
    describe('Loading Spinner', () => {
      it('waits for spinner to hide', () => {
        cy.visit('/p/playwright-labs.html#loading-spinner');
        cy.get('#submit-spinner').click();
        cy.get('#lab-spinner').should('not.be.visible', { timeout: 5000 });
        cy.get('#result-spinner').should('have.text', 'Done!');
      });
    });
    describe('Loading Spinner', () => {
      it('waits for spinner to hide', () => {
        cy.visit('/p/playwright-labs.html#loading-spinner');
        cy.get('#submit-spinner').click();
        cy.get('#lab-spinner').should('not.be.visible', { timeout: 5000 });
        cy.get('#result-spinner').should('have.text', 'Done!');
      });
    });
    hard Cypress

    Retry Logic & Flakiness

    Configure retries at the runner level and detect flaky tests using the HTML reporter.

    #45
    Live widget (shared with Playwright)
    Challenge: The button below is intentionally flaky (70% success). Configure retries to handle this.
    Expected: Test passes within retries, marked flaky in report.
    Interactive area
    JavaScript TypeScript
    class=class="s">"c">// cypress.config.js
    const { defineConfig } = require('cypress');
    module.exports = defineConfig({
      e2e: {
        retries: 2,
        reporter: 'mochawesome',
        reporterOptions: { reportDir: 'cypress/reports', overwrite: false }
      }
    });
    
    class=class="s">"c">// spec
    describe('Retry Logic', () => {
      it('handles flaky button', () => {
        cy.visit('/p/playwright-labs.html#retry-logic');
        cy.get('#flaky-button').click();
        cy.get('#result-retry').should('have.text', 'Success');
      });
    });
    class=class="s">"c">// cypress.config.ts
    import { defineConfig } from 'cypress';
    export default defineConfig({
      e2e: {
        retries: 2,
        reporter: 'mochawesome',
        reporterOptions: { reportDir: 'cypress/reports', overwrite: false }
      }
    });
    
    class=class="s">"c">// spec
    describe('Retry Logic', () => {
      it('handles flaky button', () => {
        cy.visit('/p/playwright-labs.html#retry-logic');
        cy.get('#flaky-button').click();
        cy.get('#result-retry').should('have.text', 'Success');
      });
    });
    medium Cypress

    XPath Practice

    Master XPath axes: parent, ancestor, following-sibling, and more. Use page.locator('xpath=...') syntax.

    #46
    Live widget (shared with Playwright)
    Challenge: Use XPath to find the button that follows the 'QA' label and click it.
    Expected: XPath-targeted button clicked.
    Interactive area
    JavaScript TypeScript
    describe('XPath Practice', () => {
      it('uses xpath selector', () => {
        cy.visit('/p/playwright-labs.html#xpath-practice');
        class=class="s">"c">// Requires cypress-xpath plugin: npm i -D cypress-xpath
        cy.xpath('class=class="s">"c">//label[text()="QA"]/following-sibling::button').click();
        cy.get('#result-xpath').should('have.text', 'XPath found me!');
      });
    });
    class=class="s">"c">// In cypress/support/e2e.js: require('cypress-xpath');
    describe('XPath Practice', () => {
      it('uses xpath selector', () => {
        cy.visit('/p/playwright-labs.html#xpath-practice');
        cy.xpath('class=class="s">"c">//label[text()="QA"]/following-sibling::button').click();
        cy.get('#result-xpath').should('have.text', 'XPath found me!');
      });
    });
    easy Cypress

    CSS Selector Practice

    Nth-child, attribute, and pseudo-class selector drills for resilient test locators.

    #47
    Live widget (shared with Playwright)
    Challenge: Use CSS :nth-child(2) to click the second item in the list.
    Expected: Second item clicked.
    Interactive area
    JavaScript TypeScript
    describe('CSS Selector Practice', () => {
      it('uses nth-child selector', () => {
        cy.visit('/p/playwright-labs.html#css-selector-practice');
        cy.get('#css-list li:nth-child(2) button').click();
        cy.get('#result-css').should('have.text', 'Item 2 clicked');
      });
    });
    describe('CSS Selector Practice', () => {
      it('uses nth-child selector', () => {
        cy.visit('/p/playwright-labs.html#css-selector-practice');
        cy.get('#css-list li:nth-child(2) button').click();
        cy.get('#result-css').should('have.text', 'Item 2 clicked');
      });
    });
    medium Cypress

    iFrame (Same Origin)

    Same-origin iframes are accessible via frameLocator() — no special permissions needed.

    #49
    Live widget (shared with Playwright)
    Challenge: Interact with the input inside the iframe and verify the echoed value.
    Expected: Iframe input captured and echoed.
    Interactive area
    JavaScript TypeScript
    describe('Frames (iFrame) — Input', () => {
      it('types into iframe input', () => {
        cy.visit('/p/playwright-labs.html#frames-iframe');
        cy.frameLoaded('#lab-iframe');
        cy.iframe('#lab-iframe').find('#iframe-input').type('Hello iframe');
        cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Hello iframe');
      });
    });
    describe('Frames (iFrame) — Input', () => {
      it('types into iframe input', () => {
        cy.visit('/p/playwright-labs.html#frames-iframe');
        cy.frameLoaded('#lab-iframe');
        cy.iframe('#lab-iframe').find('#iframe-input').type('Hello iframe');
        cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Hello iframe');
      });
    });
    hard Cypress

    Multi-Window Orchestration

    Coordinate multiple popup windows in a single test by tracking them via context.pages.

    #50
    Live widget (shared with Playwright)
    Challenge: Open two popups, interact with both, then close them.
    Expected: Both popups accessible and closable.
    Interactive area
    Open via link ↗
    Popups open about:blank — replace URL in your test.
    JavaScript TypeScript
    describe('Multi-Window Orchestration', () => {
      it('stubs window.open', () => {
        cy.visit('/p/playwright-labs.html#multi-window-orchestration');
        const openStub = cy.stub().returns({
          document: { write: cy.stub(), close: cy.stub() }
        });
        cy.window().then(win => cy.stub(win, 'open').callsFake(openStub));
        cy.get('#popup-button').click();
        cy.wrap(openStub).should('be.calledOnce');
      });
    });
    describe('Multi-Window Orchestration', () => {
      it('stubs window.open', () => {
        cy.visit('/p/playwright-labs.html#multi-window-orchestration');
        const openStub = cy.stub().returns({
          document: { write: cy.stub(), close: cy.stub() }
        });
        cy.window().then((win) => cy.stub(win, 'open').callsFake(openStub));
        cy.get('#popup-button').click();
        cy.wrap(openStub).should('be.calledOnce');
      });
    });
    medium Cypress

    iFrames

    Standard same-origin iframe interaction — the bread and butter of legacy automation.

    #51
    Live widget (shared with Playwright)
    Challenge: Switch into the iframe and click the button inside.
    Expected: Button inside iframe clicked.
    Interactive area
    JavaScript TypeScript
    describe('iFrames', () => {
      it('interacts with iframe content', () => {
        cy.visit('/p/playwright-labs.html#iframes');
        cy.frameLoaded('#lab-iframe');
        cy.iframe('#lab-iframe').find('#iframe-button').click();
        cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Iframe clicked!');
      });
    });
    describe('iFrames', () => {
      it('interacts with iframe content', () => {
        cy.visit('/p/playwright-labs.html#iframes');
        cy.frameLoaded('#lab-iframe');
        cy.iframe('#lab-iframe').find('#iframe-button').click();
        cy.iframe('#lab-iframe').find('#iframe-result').should('have.text', 'Iframe clicked!');
      });
    });
    medium Cypress

    Popup Windows

    Popup windows opened via target=_blank or window.open() — captured via waitForEvent('popup').

    #52
    Live widget (shared with Playwright)
    Challenge: Click the link with target=_blank and verify the popup loads.
    Expected: Popup loads with correct content.
    Interactive area
    Open via link ↗
    Popups open about:blank — replace URL in your test.
    JavaScript TypeScript
    describe('Popup Windows (target=_blank)', () => {
      it('removes target and clicks', () => {
        cy.visit('/p/playwright-labs.html#multiple-tabs-popup');
        cy.get('#popup-link').invoke('removeAttr', 'target').click();
      });
    });
    describe('Popup Windows (target=_blank)', () => {
      it('removes target and clicks', () => {
        cy.visit('/p/playwright-labs.html#multiple-tabs-popup');
        cy.get('#popup-link').invoke('removeAttr', 'target').click();
      });
    });