{
  "doc": {
    "id": "app/end-to-end-testing/testing-your-app",
    "title": "Effective E2E: Cypress App Testing",
    "description": "Discover effective end-to-end testing strategies in Cypress App Testing Guide. Elevate your testing proficiency",
    "section": "app",
    "source_path": "/llm/markdown/app/end-to-end-testing/testing-your-app.md",
    "version": "3cf5b86b3403f604bdf7f3e35025c3bc3865e02c",
    "updated_at": "2026-05-07T17:44:31.931Z",
    "headings": [
      {
        "id": "app/end-to-end-testing/testing-your-app#testing-your-app",
        "text": "Testing Your App",
        "level": 1
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#what-youll-learn",
        "text": "What you'll learn",
        "level": 5
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#step-1-start-your-server",
        "text": "Step 1: Start your server",
        "level": 2
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#why-start-a-local-development-server",
        "text": "Why start a local development server?",
        "level": 3
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#step-2-visit-your-server",
        "text": "Step 2: Visit your server",
        "level": 2
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#step-3-configure-cypress",
        "text": "Step 3: Configure Cypress",
        "level": 2
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#testing-strategies",
        "text": "Testing strategies",
        "level": 2
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#seeding-data",
        "text": "Seeding data",
        "level": 3
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#stubbing-the-server",
        "text": "Stubbing the server",
        "level": 3
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#generate-the-fixture-stubs-ahead-of-time",
        "text": "Generate the fixture stubs ahead of time",
        "level": 4
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#write-a-single-e2e-test-without-stubs-and-then-stub-the-rest",
        "text": "Write a single e2e test without stubs, and then stub the rest",
        "level": 4
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#logging-in",
        "text": "Logging in",
        "level": 3
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#fully-test-the-login-flow-but-only-once",
        "text": "Fully test the login flow -- but only once!",
        "level": 4
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#reusing-the-login-code",
        "text": "Reusing the login code",
        "level": 4
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#improving-performance",
        "text": "Improving performance",
        "level": 4
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#testing-apps-you-dont-control",
        "text": "Testing Apps You Don't Control",
        "level": 2
      },
      {
        "id": "app/end-to-end-testing/testing-your-app#get-started",
        "text": "Get started",
        "level": 2
      }
    ]
  },
  "chunks": [
    {
      "id": "app/end-to-end-testing/testing-your-app#what-youll-learn",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "What you'll learn",
      "heading_level": 5,
      "content_markdown": "##### What you'll learn\n\n*   How to start your server and test your application with Cypress\n*   How to configure Cypress\n*   Strategies for testing your application including seeding data, stubbing the server, and logging in\n",
      "section": "app",
      "anchors": [
        "what-youll-learn"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 48
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#step-1-start-your-server",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Step 1: Start your server",
      "heading_level": 2,
      "content_markdown": "## Step 1: Start your server\n\nAssuming you've successfully [installed Cypress](/llm/markdown/app/get-started/install-cypress.md) and [opened Cypress](/llm/markdown/app/get-started/open-the-app.md) in your project, the first thing you'll want to do is start your local development server that hosts the application.\n\nIt should look something like **[http://localhost:8080](http://localhost:8080)**.\n\n**Anti-Pattern**\n\nDon't try to start a web server from within Cypress scripts. Read about [best practices](/llm/markdown/app/core-concepts/best-practices.md#Web-Servers) here.\n\n### Why start a local development server?\n\nYou may be wondering - why can't I just visit my application that's already in production?\n\nWhile you certainly _can_ test an application that's already deployed, that's not really the **sweet spot** of Cypress.\n\nCypress is built, and optimized around being a tool for your daily local development. In fact, after you start using Cypress for awhile, we believe that you may find it useful to even do **all of your development** in it.\n\nUltimately you'll not only be able to **test and develop** at the same time, but you'll actually be able to build your application **faster** while getting tests \"for free\".\n\nWhat's more - since Cypress enables you to do things like **stub network requests** you can build out your web application without even needing a server to provide valid JSON responses.\n\nLast but not least - trying to shoehorn tests to an already built application is much more difficult than building it as you write tests. You'll likely encounter a series of initial up front challenges / hurdles that would have otherwise been avoided writing tests from the start.\n\nThe last, and probably most important reason why you want to test against local servers, is the ability to **control them**. When your application is running in production you can't control it.\n\nWhen it's running in development you can:\n\n*   take shortcuts\n*   seed data by running executable scripts\n*   expose test environment specific routes\n*   disable security features which make automation difficult\n*   reset state on the server / database\n\nWith that said - you still have the option to have it **both ways**.\n\nMany of our users run the _majority_ of their integration tests against a local development server, but then reserve a smaller set of **smoke tests** that run only against a deployed production app.\n",
      "section": "app",
      "anchors": [
        "step-1-start-your-server"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 485
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#why-start-a-local-development-server",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Why start a local development server?",
      "heading_level": 3,
      "content_markdown": "### Why start a local development server?\n\nYou may be wondering - why can't I just visit my application that's already in production?\n\nWhile you certainly _can_ test an application that's already deployed, that's not really the **sweet spot** of Cypress.\n\nCypress is built, and optimized around being a tool for your daily local development. In fact, after you start using Cypress for awhile, we believe that you may find it useful to even do **all of your development** in it.\n\nUltimately you'll not only be able to **test and develop** at the same time, but you'll actually be able to build your application **faster** while getting tests \"for free\".\n\nWhat's more - since Cypress enables you to do things like **stub network requests** you can build out your web application without even needing a server to provide valid JSON responses.\n\nLast but not least - trying to shoehorn tests to an already built application is much more difficult than building it as you write tests. You'll likely encounter a series of initial up front challenges / hurdles that would have otherwise been avoided writing tests from the start.\n\nThe last, and probably most important reason why you want to test against local servers, is the ability to **control them**. When your application is running in production you can't control it.\n\nWhen it's running in development you can:\n\n*   take shortcuts\n*   seed data by running executable scripts\n*   expose test environment specific routes\n*   disable security features which make automation difficult\n*   reset state on the server / database\n\nWith that said - you still have the option to have it **both ways**.\n\nMany of our users run the _majority_ of their integration tests against a local development server, but then reserve a smaller set of **smoke tests** that run only against a deployed production app.\n",
      "section": "app",
      "anchors": [
        "why-start-a-local-development-server"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 409
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#step-2-visit-your-server",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Step 2: Visit your server",
      "heading_level": 2,
      "content_markdown": "## Step 2: Visit your server\n\nOnce your server is running, it's time to visit it.\n\nLet's delete the `spec.cy.js` file created in the previous tutorial now that it's no longer needed.\n\n```\nrm cypress/e2e/spec.cy.js\n```\n\nNow let's create our own spec file called `home_page.cy.js`.\n\n```\ntouch cypress/e2e/home_page.cy.js\n```\n\nOnce that file is created, you should see it in the list of spec files.\n\nNow you'll need to add in the following code in your test file to visit your server:\n\n```\ndescribe('The Home Page', () => {  it('successfully loads', () => {    cy.visit('http://localhost:8080') // change URL to match your dev URL  })})\n```\n\nNow click on the `home_page.cy.js` file and watch Cypress open your browser.\n\nIf you forgot to start your server you'll see the error below:\n\nIf you've started your server, then you should see your application loaded and working.\n",
      "section": "app",
      "anchors": [
        "step-2-visit-your-server"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 189
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#step-3-configure-cypress",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Step 3: Configure Cypress",
      "heading_level": 2,
      "content_markdown": "## Step 3: Configure Cypress\n\nIf you think ahead, you'll quickly realize that you're going to be typing this URL a lot, since every test is going to need to visit some page of your application. Luckily, Cypress provides a [configuration option](/llm/markdown/app/references/configuration.md) for this. Let's leverage that now.\n\nOpen up your [configuration file](/llm/markdown/app/references/configuration.md). It starts out empty, but let's add the `baseUrl` option.\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  e2e: {    baseUrl: 'http://localhost:8080',  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  e2e: {    baseUrl: 'http://localhost:8080',  },})\n```\n\nThis will automatically **prefix** [`cy.visit()`](/llm/markdown/api/commands/visit.md) and [`cy.request()`](/llm/markdown/api/commands/request.md) commands with this baseUrl.\n\nWhenever you modify your configuration file, Cypress will automatically reboot itself and kill any open browsers. This is normal. Click on the spec file again to relaunch the browser.\n\nWe can now visit a relative path and omit the hostname and port.\n\n```\ndescribe('The Home Page', () => {  it('successfully loads', () => {    cy.visit('/')  })})\n```\n\nGreat! Everything should still be green.\n\n**Configuration Options**\n\nCypress has many more configuration options you can use to customize its behavior. Things like where your tests live, default timeout periods, environment variables, which reporter to use, etc.\n\nCheck them out in [Configuration](/llm/markdown/app/references/configuration.md)!\n",
      "section": "app",
      "anchors": [
        "step-3-configure-cypress"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 276
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#testing-strategies",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Testing strategies",
      "heading_level": 2,
      "content_markdown": "## Testing strategies\n\nYou're about to embark on writing tests for your application, and only _you_ know your application, so we don't have a lot of specific advice to give you.\n\n**What to test, where the edge cases and seams are, what regressions you're likely to run into, etc. are entirely up to you, your application, and your team.**\n\nThat said, modern web testing has a few wrinkles that every team experiences, so here are some quick tips on common situations you're likely to run into.\n\n### Seeding data\n\nDepending on how your application is built - it's likely that your web application is going to be affected and controlled by the server.\n\nTypically these days servers communicate with front end apps via JSON, but you could also be running a traditional server-side rendered HTML web application.\n\nGenerally the server is responsible for sending responses that reflect some kind of **state** it holds - generally in a database.\n\nTraditionally when writing `e2e` tests using Selenium, before you automate the browser you do some kind of **set up and tear down** on the server.\n\nPerhaps you'll need to generate a user, and seed them with associations and records. You may be familiar with using things such as fixtures or factories.\n\nTo test various page states - like an empty view, or a pagination view, you'd need to seed the server so that this state can be tested.\n\n**While there is a lot more to this strategy, you generally have three ways to facilitate this with Cypress:**\n\n*   [`cy.exec()`](/llm/markdown/api/commands/exec.md) - to run system commands\n*   [`cy.task()`](/llm/markdown/api/commands/task.md) - to run code in Node via the [setupNodeEvents](/llm/markdown/app/plugins/plugins-guide.md#Using-a-plugin) function\n*   [`cy.request()`](/llm/markdown/api/commands/request.md) - to make HTTP requests\n\nIf you're running `node.js` on your server, you might add a `before` or `beforeEach` hook that executes an `npm` task.\n\n```\ndescribe('The Home Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')  })  it('successfully loads', () => {    cy.visit('/')  })})\n```\n\nInstead of just executing a system command, you may want more flexibility and could expose a series of routes only when running in a test environment.\n\n**For instance, you could compose several requests together to tell your server exactly the state you want to create.**\n\n```\ndescribe('The Home Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')    // seed a post in the DB that we control from our tests    cy.request('POST', '/test/seed/post', {      title: 'First Post',      authorId: 1,      body: '...',    })    // seed a user in the DB that we can control from our tests    cy.request('POST', '/test/seed/user', { name: 'Jane' })      .its('body')      .as('currentUser')  })  it('successfully loads', () => {    // this.currentUser will now point to the response    // body of the cy.request() that we could use    // to log in or work with in some way    cy.visit('/')  })})\n```\n\nWhile there's nothing really _wrong_ with this approach, it does add a lot of complexity. You will be battling synchronizing the state between your server and your browser - and you'll always need to set up / tear down this state before tests (which is slow).\n\nThe good news is that we aren't Selenium, nor are we a traditional e2e testing tool. That means we're not bound to the same restrictions.\n\n**With Cypress, there are several other approaches that can offer an arguably better and faster experience.**\n\n### Stubbing the server\n\nAnother valid approach opposed to seeding and talking to your server is to bypass it altogether.\n\nWhile you'll still receive all of the regular HTML / JS / CSS assets from your server and you'll continue to [`cy.visit()`](/llm/markdown/api/commands/visit.md) it in the same way - you can instead **stub** the JSON responses coming from it.\n\nThis means that instead of resetting the database, or seeding it with the state we want, you can force the server to respond with **whatever** you want it to. In this way, we not only prevent needing to synchronize the state between the server and browser, but we also prevent mutating state from our tests. That means tests won't build up state that may affect other tests.\n\nAnother upside is that this enables you to **build out your application** without needing the _contract_ of the server to exist. You can build it the way you want the data to be structured, and even test all of the edge cases, without needing a server.\n\nHowever - there is likely still a balance here where **both** strategies are valid (and you should likely do them).\n\nWhile stubbing is great, it means that you don't have the guarantees that these response payloads actually match what the server will send. However, there are still many valid ways to get around this:\n\n#### Generate the fixture stubs ahead of time\n\nYou could have the server generate all of the fixture stubs for you ahead of time. This means their data will reflect what the server will actually send.\n\n#### Write a single e2e test without stubs, and then stub the rest\n\nAnother more balanced approach is to integrate both strategies. You likely want to have a **single test** that takes a true `e2e` approach and stubs nothing. It'll use the feature for real - including seeding the database and setting up state.\n\nOnce you've established it's working you can then use stubs to test all of the edge cases and additional scenarios. There are no benefits to using real data in the vast majority of cases. We recommend that the vast majority of tests use stub data. They will be orders of magnitude faster, and much less complex.\n\n**Guide: Network Requests**\n\nPlease read our [Guide on Network Requests](/llm/markdown/app/guides/network-requests.md) for a much more thorough analysis and approach to accomplishing this.\n\n### Logging in\n\nOne of the first (and arguably one of the hardest) hurdles you'll have to overcome in testing is logging into your application.\n\nNothing slows a test suite down like having to log in, but all the good parts of your application most likely require an authenticated user! Here are some tips.\n\n#### Fully test the login flow -- but only once!\n\nIt's a great idea to get your signup and login flow under test coverage since it is very important to all of your users and you never want it to break.\n\nLogging in is one of those features that are **mission critical** and should likely involve your server. We recommend you test signup and login using your UI as a real user would:\n\nHere's an example alongside seeding your database:\n\n```\ndescribe('The Login Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')    // seed a user in the DB that we can control from our tests    // assuming it generates a random password for us    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })      .its('body')      .as('currentUser')  })  it('sets auth cookie when logging in via form submission', function () {    // destructuring assignment of the this.currentUser object    const { username, password } = this.currentUser    cy.visit('/login')    cy.get('input[name=username]').type(username)    // {enter} causes the form to submit    cy.get('input[name=password]').type(`${password}{enter}`)    // we should be redirected to /dashboard    cy.url().should('include', '/dashboard')    // our auth cookie should be present    cy.getCookie('your-session-cookie').should('exist')    // UI should reflect this user being logged in    cy.get('h1').should('contain', 'jane.lane')  })})\n```\n\nYou'll likely also want to test your login UI for:\n\n*   Invalid username / password\n*   Username taken\n*   Password complexity requirements\n*   Edge cases like locked / deleted accounts\n\nEach of these likely requires a full blown e2e test.\n\n#### Reusing the login code\n\nAt this point there's nothing stopping you copying and pasting the login code above into every one of your tests that needs an authenticated user. Or you could even put all your tests in one big spec file and put the login code in a `beforeEach` block. But neither of those approaches is particularly maintainable, and they're certainly not very elegant. A much better solution is to write a custom `cy.login()` [command](/llm/markdown/api/cypress-api/custom-commands.md).\n\nCustom commands allow you to easily encapsulate and reuse Cypress test logic. They let you add your own functionality to your test suite and then use it with the same [chainable and asynchronous API](/llm/markdown/app/core-concepts/introduction-to-cypress.md#The-Cypress-Command-Queue) as the built-in Cypress commands. Lets make the above login example a custom command and add it to `cypress/support/commands.js` so it can be leveraged in any spec file:\n\n```\n// In cypress/support/commands.jsCypress.Commands.add('login', (username, password) => {  cy.visit('/login')  cy.get('input[name=username]').type(username)  // {enter} causes the form to submit  cy.get('input[name=password]').type(`${password}{enter}`, { log: false })  // we should be redirected to /dashboard  cy.url().should('include', '/dashboard')  // our auth cookie should be present  cy.getCookie('your-session-cookie').should('exist')  // UI should reflect this user being logged in  cy.get('h1').should('contain', username)})// In your spec fileit('does something on a secured page', function () {  const { username, password } = this.currentUser  cy.login(username, password)  // ...rest of test})\n```\n\n#### Improving performance\n\nYou're probably wondering what happened to our advice about logging in \"only once\". The custom command above will work just fine for testing your secured pages, but if you have more than a handful of tests, logging in before every test is going to increase the overall run time of your suite.\n\nLuckily, Cypress provides the [`cy.session()`](/llm/markdown/api/commands/session.md) command, a powerful performance tool that lets you cache the browser context associated with your user and reuse it for multiple tests without going through multiple login flows! Let's modify the custom `cy.login()` command from our previous example to use `cy.session()`:\n\n```\nCypress.Commands.add('login', (username, password) => {  cy.session(    username,    () => {      cy.visit('/login')      cy.get('input[name=username]').type(username)      cy.get('input[name=password]').type(`${password}{enter}`, { log: false })      cy.url().should('include', '/dashboard')      cy.get('h1').should('contain', username)    },    {      validate: () => {        cy.getCookie('your-session-cookie').should('exist')      },    }  )})\n```\n\n**Third-Party Login**\n\nIf your app implements login via a third-party authentication provider such as [Auth0](https://auth0.com/) or [Okta](https://www.okta.com/), you can use the [`cy.origin()`](/llm/markdown/api/commands/origin.md) command to include their login pages as part of your authentication tests.\n\nThere's a lot going on here that's out of the scope for this introduction. Please check out the [`cy.session()`](/llm/markdown/api/commands/session.md) documentation for a more in-depth explanation.\n\n**Authentication Recipes**\n\nLogging in can be more complex than what we've just covered.\n\nWe've created several recipes covering additional scenarios like dealing with CSRF tokens or testing XHR based login forms.\n\nFeel free to [explore these additional logging in](/llm/markdown/app/references/recipes.md) recipes.\n",
      "section": "app",
      "anchors": [
        "testing-strategies"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 2289
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#seeding-data",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Seeding data",
      "heading_level": 3,
      "content_markdown": "### Seeding data\n\nDepending on how your application is built - it's likely that your web application is going to be affected and controlled by the server.\n\nTypically these days servers communicate with front end apps via JSON, but you could also be running a traditional server-side rendered HTML web application.\n\nGenerally the server is responsible for sending responses that reflect some kind of **state** it holds - generally in a database.\n\nTraditionally when writing `e2e` tests using Selenium, before you automate the browser you do some kind of **set up and tear down** on the server.\n\nPerhaps you'll need to generate a user, and seed them with associations and records. You may be familiar with using things such as fixtures or factories.\n\nTo test various page states - like an empty view, or a pagination view, you'd need to seed the server so that this state can be tested.\n\n**While there is a lot more to this strategy, you generally have three ways to facilitate this with Cypress:**\n\n*   [`cy.exec()`](/llm/markdown/api/commands/exec.md) - to run system commands\n*   [`cy.task()`](/llm/markdown/api/commands/task.md) - to run code in Node via the [setupNodeEvents](/llm/markdown/app/plugins/plugins-guide.md#Using-a-plugin) function\n*   [`cy.request()`](/llm/markdown/api/commands/request.md) - to make HTTP requests\n\nIf you're running `node.js` on your server, you might add a `before` or `beforeEach` hook that executes an `npm` task.\n\n```\ndescribe('The Home Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')  })  it('successfully loads', () => {    cy.visit('/')  })})\n```\n\nInstead of just executing a system command, you may want more flexibility and could expose a series of routes only when running in a test environment.\n\n**For instance, you could compose several requests together to tell your server exactly the state you want to create.**\n\n```\ndescribe('The Home Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')    // seed a post in the DB that we control from our tests    cy.request('POST', '/test/seed/post', {      title: 'First Post',      authorId: 1,      body: '...',    })    // seed a user in the DB that we can control from our tests    cy.request('POST', '/test/seed/user', { name: 'Jane' })      .its('body')      .as('currentUser')  })  it('successfully loads', () => {    // this.currentUser will now point to the response    // body of the cy.request() that we could use    // to log in or work with in some way    cy.visit('/')  })})\n```\n\nWhile there's nothing really _wrong_ with this approach, it does add a lot of complexity. You will be battling synchronizing the state between your server and your browser - and you'll always need to set up / tear down this state before tests (which is slow).\n\nThe good news is that we aren't Selenium, nor are we a traditional e2e testing tool. That means we're not bound to the same restrictions.\n\n**With Cypress, there are several other approaches that can offer an arguably better and faster experience.**\n",
      "section": "app",
      "anchors": [
        "seeding-data"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 656
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#stubbing-the-server",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Stubbing the server",
      "heading_level": 3,
      "content_markdown": "### Stubbing the server\n\nAnother valid approach opposed to seeding and talking to your server is to bypass it altogether.\n\nWhile you'll still receive all of the regular HTML / JS / CSS assets from your server and you'll continue to [`cy.visit()`](/llm/markdown/api/commands/visit.md) it in the same way - you can instead **stub** the JSON responses coming from it.\n\nThis means that instead of resetting the database, or seeding it with the state we want, you can force the server to respond with **whatever** you want it to. In this way, we not only prevent needing to synchronize the state between the server and browser, but we also prevent mutating state from our tests. That means tests won't build up state that may affect other tests.\n\nAnother upside is that this enables you to **build out your application** without needing the _contract_ of the server to exist. You can build it the way you want the data to be structured, and even test all of the edge cases, without needing a server.\n\nHowever - there is likely still a balance here where **both** strategies are valid (and you should likely do them).\n\nWhile stubbing is great, it means that you don't have the guarantees that these response payloads actually match what the server will send. However, there are still many valid ways to get around this:\n\n#### Generate the fixture stubs ahead of time\n\nYou could have the server generate all of the fixture stubs for you ahead of time. This means their data will reflect what the server will actually send.\n\n#### Write a single e2e test without stubs, and then stub the rest\n\nAnother more balanced approach is to integrate both strategies. You likely want to have a **single test** that takes a true `e2e` approach and stubs nothing. It'll use the feature for real - including seeding the database and setting up state.\n\nOnce you've established it's working you can then use stubs to test all of the edge cases and additional scenarios. There are no benefits to using real data in the vast majority of cases. We recommend that the vast majority of tests use stub data. They will be orders of magnitude faster, and much less complex.\n\n**Guide: Network Requests**\n\nPlease read our [Guide on Network Requests](/llm/markdown/app/guides/network-requests.md) for a much more thorough analysis and approach to accomplishing this.\n",
      "section": "app",
      "anchors": [
        "stubbing-the-server"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 523
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#generate-the-fixture-stubs-ahead-of-time",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Generate the fixture stubs ahead of time",
      "heading_level": 4,
      "content_markdown": "#### Generate the fixture stubs ahead of time\n\nYou could have the server generate all of the fixture stubs for you ahead of time. This means their data will reflect what the server will actually send.\n",
      "section": "app",
      "anchors": [
        "generate-the-fixture-stubs-ahead-of-time"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 48
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#write-a-single-e2e-test-without-stubs-and-then-stub-the-rest",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Write a single e2e test without stubs, and then stub the rest",
      "heading_level": 4,
      "content_markdown": "#### Write a single e2e test without stubs, and then stub the rest\n\nAnother more balanced approach is to integrate both strategies. You likely want to have a **single test** that takes a true `e2e` approach and stubs nothing. It'll use the feature for real - including seeding the database and setting up state.\n\nOnce you've established it's working you can then use stubs to test all of the edge cases and additional scenarios. There are no benefits to using real data in the vast majority of cases. We recommend that the vast majority of tests use stub data. They will be orders of magnitude faster, and much less complex.\n\n**Guide: Network Requests**\n\nPlease read our [Guide on Network Requests](/llm/markdown/app/guides/network-requests.md) for a much more thorough analysis and approach to accomplishing this.\n",
      "section": "app",
      "anchors": [
        "write-a-single-e2e-test-without-stubs-and-then-stub-the-rest"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 175
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#logging-in",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Logging in",
      "heading_level": 3,
      "content_markdown": "### Logging in\n\nOne of the first (and arguably one of the hardest) hurdles you'll have to overcome in testing is logging into your application.\n\nNothing slows a test suite down like having to log in, but all the good parts of your application most likely require an authenticated user! Here are some tips.\n\n#### Fully test the login flow -- but only once!\n\nIt's a great idea to get your signup and login flow under test coverage since it is very important to all of your users and you never want it to break.\n\nLogging in is one of those features that are **mission critical** and should likely involve your server. We recommend you test signup and login using your UI as a real user would:\n\nHere's an example alongside seeding your database:\n\n```\ndescribe('The Login Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')    // seed a user in the DB that we can control from our tests    // assuming it generates a random password for us    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })      .its('body')      .as('currentUser')  })  it('sets auth cookie when logging in via form submission', function () {    // destructuring assignment of the this.currentUser object    const { username, password } = this.currentUser    cy.visit('/login')    cy.get('input[name=username]').type(username)    // {enter} causes the form to submit    cy.get('input[name=password]').type(`${password}{enter}`)    // we should be redirected to /dashboard    cy.url().should('include', '/dashboard')    // our auth cookie should be present    cy.getCookie('your-session-cookie').should('exist')    // UI should reflect this user being logged in    cy.get('h1').should('contain', 'jane.lane')  })})\n```\n\nYou'll likely also want to test your login UI for:\n\n*   Invalid username / password\n*   Username taken\n*   Password complexity requirements\n*   Edge cases like locked / deleted accounts\n\nEach of these likely requires a full blown e2e test.\n\n#### Reusing the login code\n\nAt this point there's nothing stopping you copying and pasting the login code above into every one of your tests that needs an authenticated user. Or you could even put all your tests in one big spec file and put the login code in a `beforeEach` block. But neither of those approaches is particularly maintainable, and they're certainly not very elegant. A much better solution is to write a custom `cy.login()` [command](/llm/markdown/api/cypress-api/custom-commands.md).\n\nCustom commands allow you to easily encapsulate and reuse Cypress test logic. They let you add your own functionality to your test suite and then use it with the same [chainable and asynchronous API](/llm/markdown/app/core-concepts/introduction-to-cypress.md#The-Cypress-Command-Queue) as the built-in Cypress commands. Lets make the above login example a custom command and add it to `cypress/support/commands.js` so it can be leveraged in any spec file:\n\n```\n// In cypress/support/commands.jsCypress.Commands.add('login', (username, password) => {  cy.visit('/login')  cy.get('input[name=username]').type(username)  // {enter} causes the form to submit  cy.get('input[name=password]').type(`${password}{enter}`, { log: false })  // we should be redirected to /dashboard  cy.url().should('include', '/dashboard')  // our auth cookie should be present  cy.getCookie('your-session-cookie').should('exist')  // UI should reflect this user being logged in  cy.get('h1').should('contain', username)})// In your spec fileit('does something on a secured page', function () {  const { username, password } = this.currentUser  cy.login(username, password)  // ...rest of test})\n```\n\n#### Improving performance\n\nYou're probably wondering what happened to our advice about logging in \"only once\". The custom command above will work just fine for testing your secured pages, but if you have more than a handful of tests, logging in before every test is going to increase the overall run time of your suite.\n\nLuckily, Cypress provides the [`cy.session()`](/llm/markdown/api/commands/session.md) command, a powerful performance tool that lets you cache the browser context associated with your user and reuse it for multiple tests without going through multiple login flows! Let's modify the custom `cy.login()` command from our previous example to use `cy.session()`:\n\n```\nCypress.Commands.add('login', (username, password) => {  cy.session(    username,    () => {      cy.visit('/login')      cy.get('input[name=username]').type(username)      cy.get('input[name=password]').type(`${password}{enter}`, { log: false })      cy.url().should('include', '/dashboard')      cy.get('h1').should('contain', username)    },    {      validate: () => {        cy.getCookie('your-session-cookie').should('exist')      },    }  )})\n```\n\n**Third-Party Login**\n\nIf your app implements login via a third-party authentication provider such as [Auth0](https://auth0.com/) or [Okta](https://www.okta.com/), you can use the [`cy.origin()`](/llm/markdown/api/commands/origin.md) command to include their login pages as part of your authentication tests.\n\nThere's a lot going on here that's out of the scope for this introduction. Please check out the [`cy.session()`](/llm/markdown/api/commands/session.md) documentation for a more in-depth explanation.\n\n**Authentication Recipes**\n\nLogging in can be more complex than what we've just covered.\n\nWe've created several recipes covering additional scenarios like dealing with CSRF tokens or testing XHR based login forms.\n\nFeel free to [explore these additional logging in](/llm/markdown/app/references/recipes.md) recipes.\n",
      "section": "app",
      "anchors": [
        "logging-in"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 996
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#fully-test-the-login-flow-but-only-once",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Fully test the login flow -- but only once!",
      "heading_level": 4,
      "content_markdown": "#### Fully test the login flow -- but only once!\n\nIt's a great idea to get your signup and login flow under test coverage since it is very important to all of your users and you never want it to break.\n\nLogging in is one of those features that are **mission critical** and should likely involve your server. We recommend you test signup and login using your UI as a real user would:\n\nHere's an example alongside seeding your database:\n\n```\ndescribe('The Login Page', () => {  beforeEach(() => {    // reset and seed the database prior to every test    cy.exec('npm run db:reset && npm run db:seed')    // seed a user in the DB that we can control from our tests    // assuming it generates a random password for us    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })      .its('body')      .as('currentUser')  })  it('sets auth cookie when logging in via form submission', function () {    // destructuring assignment of the this.currentUser object    const { username, password } = this.currentUser    cy.visit('/login')    cy.get('input[name=username]').type(username)    // {enter} causes the form to submit    cy.get('input[name=password]').type(`${password}{enter}`)    // we should be redirected to /dashboard    cy.url().should('include', '/dashboard')    // our auth cookie should be present    cy.getCookie('your-session-cookie').should('exist')    // UI should reflect this user being logged in    cy.get('h1').should('contain', 'jane.lane')  })})\n```\n\nYou'll likely also want to test your login UI for:\n\n*   Invalid username / password\n*   Username taken\n*   Password complexity requirements\n*   Edge cases like locked / deleted accounts\n\nEach of these likely requires a full blown e2e test.\n",
      "section": "app",
      "anchors": [
        "fully-test-the-login-flow-but-only-once"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 327
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#reusing-the-login-code",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Reusing the login code",
      "heading_level": 4,
      "content_markdown": "#### Reusing the login code\n\nAt this point there's nothing stopping you copying and pasting the login code above into every one of your tests that needs an authenticated user. Or you could even put all your tests in one big spec file and put the login code in a `beforeEach` block. But neither of those approaches is particularly maintainable, and they're certainly not very elegant. A much better solution is to write a custom `cy.login()` [command](/llm/markdown/api/cypress-api/custom-commands.md).\n\nCustom commands allow you to easily encapsulate and reuse Cypress test logic. They let you add your own functionality to your test suite and then use it with the same [chainable and asynchronous API](/llm/markdown/app/core-concepts/introduction-to-cypress.md#The-Cypress-Command-Queue) as the built-in Cypress commands. Lets make the above login example a custom command and add it to `cypress/support/commands.js` so it can be leveraged in any spec file:\n\n```\n// In cypress/support/commands.jsCypress.Commands.add('login', (username, password) => {  cy.visit('/login')  cy.get('input[name=username]').type(username)  // {enter} causes the form to submit  cy.get('input[name=password]').type(`${password}{enter}`, { log: false })  // we should be redirected to /dashboard  cy.url().should('include', '/dashboard')  // our auth cookie should be present  cy.getCookie('your-session-cookie').should('exist')  // UI should reflect this user being logged in  cy.get('h1').should('contain', username)})// In your spec fileit('does something on a secured page', function () {  const { username, password } = this.currentUser  cy.login(username, password)  // ...rest of test})\n```\n",
      "section": "app",
      "anchors": [
        "reusing-the-login-code"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 287
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#improving-performance",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Improving performance",
      "heading_level": 4,
      "content_markdown": "#### Improving performance\n\nYou're probably wondering what happened to our advice about logging in \"only once\". The custom command above will work just fine for testing your secured pages, but if you have more than a handful of tests, logging in before every test is going to increase the overall run time of your suite.\n\nLuckily, Cypress provides the [`cy.session()`](/llm/markdown/api/commands/session.md) command, a powerful performance tool that lets you cache the browser context associated with your user and reuse it for multiple tests without going through multiple login flows! Let's modify the custom `cy.login()` command from our previous example to use `cy.session()`:\n\n```\nCypress.Commands.add('login', (username, password) => {  cy.session(    username,    () => {      cy.visit('/login')      cy.get('input[name=username]').type(username)      cy.get('input[name=password]').type(`${password}{enter}`, { log: false })      cy.url().should('include', '/dashboard')      cy.get('h1').should('contain', username)    },    {      validate: () => {        cy.getCookie('your-session-cookie').should('exist')      },    }  )})\n```\n\n**Third-Party Login**\n\nIf your app implements login via a third-party authentication provider such as [Auth0](https://auth0.com/) or [Okta](https://www.okta.com/), you can use the [`cy.origin()`](/llm/markdown/api/commands/origin.md) command to include their login pages as part of your authentication tests.\n\nThere's a lot going on here that's out of the scope for this introduction. Please check out the [`cy.session()`](/llm/markdown/api/commands/session.md) documentation for a more in-depth explanation.\n\n**Authentication Recipes**\n\nLogging in can be more complex than what we've just covered.\n\nWe've created several recipes covering additional scenarios like dealing with CSRF tokens or testing XHR based login forms.\n\nFeel free to [explore these additional logging in](/llm/markdown/app/references/recipes.md) recipes.\n",
      "section": "app",
      "anchors": [
        "improving-performance"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 311
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#testing-apps-you-dont-control",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Testing Apps You Don't Control",
      "heading_level": 2,
      "content_markdown": "## Testing Apps You Don't Control\n\n**Anti-Pattern:** Trying to visit or interact with sites or servers you do not control.\n\nThe point of Cypress is to be a tool you use every day to build and test your own applications, not a general purpose web automation tool. You should think carefully about testing applications you **don't control** or haven't been invited to test.\n\nWhy?\n\n*   They may have security features enabled which prevent Cypress from working, such as detecting Cypress script usage. This can block your access and make it appear that the application website is unresponsive.\n*   They have the potential to change at any moment which will break tests.\n*   They may do A/B testing which makes it impossible to get consistent results.\n\nHowever, this is a guideline rather than a hard-and-fast rule. There are legitimate exceptions:\n\n*   They are specifically designed to integrate with third parties, e.g. SSO providers.\n*   They provide you with a complementary service, e.g. SaaS control panels or analytics.\n*   They reuse your content or provide plugins for an app you control.\n\nThe key is to carefully weigh the benefits of the tests against the possible disruption and flake these sorts of tests can introduce. See also [Visiting External Sites](/llm/markdown/app/core-concepts/best-practices.md#Visiting-External-Sites) for strategies when this is necessary.\n",
      "section": "app",
      "anchors": [
        "testing-apps-you-dont-control"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 284
    },
    {
      "id": "app/end-to-end-testing/testing-your-app#get-started",
      "doc_id": "app/end-to-end-testing/testing-your-app",
      "heading": "Get started",
      "heading_level": 2,
      "content_markdown": "## Get started\n\nOk, we're done talking. Now dive in and get started testing your app!\n\nFrom here you may want to explore some more of our guides:\n\n*   [Cypress API](/llm/markdown/api/table-of-contents.md) to learn what commands are available as you work\n*   [Introduction to Cypress](/llm/markdown/app/core-concepts/introduction-to-cypress.md) explains how Cypress _really_ works\n*   [Command Line](/llm/markdown/app/references/command-line.md) for running all your tests outside via `cypress run`\n*   [Continuous Integration](/llm/markdown/app/continuous-integration/overview.md) for running Cypress in CI\n*   [Cross Browser Testing](/llm/markdown/app/guides/cross-browser-testing.md) for optimally running tests in CI across Firefox and Chrome-family browsers\n*   [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app) demonstrations of Cypress testing practices, configuration, and strategies in a real-world project.\n",
      "section": "app",
      "anchors": [
        "get-started"
      ],
      "path": "/llm/json/chunked/app/end-to-end-testing/testing-your-app.json",
      "token_estimate": 135
    }
  ]
}