Aliases and Variables — Sharing Data Between Commands with cy.as and cy.wrap

Cypress commands are asynchronous and chained — you cannot store a cy.get() result in a variable the way you store a find_element() result in Selenium. So how do you reference an element, an intercepted response, or a computed value later in your test? The answer is aliases. Cypress’s .as() command saves a reference that you can retrieve with cy.get('@aliasName') or access in hooks and assertions.

Aliases — Referencing Elements, Routes and Values

Aliases solve the “I need this value later” problem without breaking the Cypress async command model.

// ── Element aliases ──

// Save an element reference for reuse
cy.get('[data-cy="product-list"]').as('productList');

// Use the alias later in the same test
cy.get('@productList').find('.product-card').should('have.length', 6);
cy.get('@productList').find('.product-card').first().click();


// ── Intercept aliases (network stubs) ──

// Alias an intercepted network request
cy.intercept('GET', '/api/products').as('getProducts');

// Trigger the request
cy.visit('/shop');

// Wait for the aliased request to complete
cy.wait('@getProducts').its('response.statusCode').should('eq', 200);

// Access the response body
cy.wait('@getProducts').then((interception) => {
  expect(interception.response.body).to.have.length(6);
  expect(interception.response.body[0]).to.have.property('name');
});


// ── Value aliases with cy.wrap() ──

// Wrap a JavaScript value so it becomes a Cypress chainable
const today = new Date().toISOString().split('T')[0];
cy.wrap(today).as('todayDate');

// Use the wrapped value later
cy.get('@todayDate').then((date) => {
  cy.get('#date-field').should('have.value', date);
});


// ── Extracting and reusing dynamic values ──

// Save a dynamically generated order ID for later verification
cy.get('[data-cy="order-id"]')
  .invoke('text')
  .as('orderId');

// Use the saved order ID in a subsequent step
cy.get('@orderId').then((orderId) => {
  cy.visit(`/orders/${orderId}`);
  cy.get('h1').should('contain.text', orderId);
});


// ── Aliases in hooks (beforeEach / afterEach) ──

describe('Shopping Cart', () => {
  beforeEach(() => {
    cy.intercept('GET', '/api/products').as('products');
    cy.intercept('POST', '/api/cart').as('addToCart');
    cy.visit('/shop');
    cy.wait('@products');
  });

  it('should add product to cart', () => {
    cy.get('.product-card').first().find('.add-btn').click();
    cy.wait('@addToCart')
      .its('response.statusCode')
      .should('eq', 201);
  });
});


// ── cy.wrap() — making non-Cypress values chainable ──

// Wrap a plain object
cy.wrap({ name: 'Alice', role: 'admin' })
  .its('name')
  .should('eq', 'Alice');

// Wrap a promise
cy.wrap(fetch('/api/health').then(r => r.json()))
  .its('status')
  .should('eq', 'ok');

// Wrap an array for iteration
cy.wrap([1, 2, 3, 4, 5]).each((num) => {
  expect(num).to.be.lessThan(10);
});
Note: Aliases created with .as() are scoped to the current test. They are automatically cleaned up between tests — you cannot reference an alias created in one it() block from another. Aliases created in beforeEach() are available in every test within that describe() block because beforeEach() runs before each test. This scoping prevents state leakage between tests and aligns with the principle of test independence.
Tip: Use cy.intercept().as('apiCall') combined with cy.wait('@apiCall') as your primary synchronisation strategy for pages that load data via API calls. Instead of waiting for DOM elements to appear (which is indirect), wait for the API response that provides the data (which is direct). This approach is both faster and more deterministic: you know the data has arrived, so the DOM update is guaranteed to follow immediately.
Warning: Do not use aliases to store values between tests. This is an anti-pattern: let savedId; in the describe block, assigned in it('creates order') and used in it('verifies order'). This creates test interdependence — if the creation test fails or is skipped, the verification test fails too. Each test should set up its own data independently.

Common Mistakes

Mistake 1 — Assigning cy.get() to a variable instead of using an alias

❌ Wrong: const btn = cy.get('.submit'); btn.click(); — Cypress commands are not assignable to variables.

✅ Correct: cy.get('.submit').as('submitBtn'); cy.get('@submitBtn').click(); — or simply chain: cy.get('.submit').click();

Mistake 2 — Not waiting for aliased intercepts before asserting on the page

❌ Wrong: cy.intercept('GET', '/api/data').as('data'); cy.visit('/page'); cy.get('.results').should('have.length', 5); — the assertion may fire before the API response arrives.

✅ Correct: cy.intercept(...).as('data'); cy.visit('/page'); cy.wait('@data'); cy.get('.results').should('have.length', 5); — wait for the data, then assert.

🧠 Test Yourself

A page loads product data from /api/products. You need to verify the API returns 200 and then check that 6 product cards render. Which approach is best?