diff --git a/cypress.json b/cypress.json
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/cypress.json
@@ -0,0 +1 @@
+{}
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
new file mode 100644
index 0000000000000000000000000000000000000000..da18d9352a17d427321962199a1fa43b8ab5cfe4
--- /dev/null
+++ b/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+  "name": "Using fixtures to represent data",
+  "email": "hello@cypress.io",
+  "body": "Fixtures are a great way to mock data for responses to routes"
+}
\ No newline at end of file
diff --git a/cypress/fixtures/profile.json b/cypress/fixtures/profile.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6c355ca5c5b9a6c63d5384a9f300a0229fdd691
--- /dev/null
+++ b/cypress/fixtures/profile.json
@@ -0,0 +1,5 @@
+{
+  "id": 8739,
+  "name": "Jane",
+  "email": "jane@example.com"
+}
\ No newline at end of file
diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json
new file mode 100644
index 0000000000000000000000000000000000000000..79b699aa772428c8c735cbc139999a3efb4dc513
--- /dev/null
+++ b/cypress/fixtures/users.json
@@ -0,0 +1,232 @@
+[
+  {
+    "id": 1,
+    "name": "Leanne Graham",
+    "username": "Bret",
+    "email": "Sincere@april.biz",
+    "address": {
+      "street": "Kulas Light",
+      "suite": "Apt. 556",
+      "city": "Gwenborough",
+      "zipcode": "92998-3874",
+      "geo": {
+        "lat": "-37.3159",
+        "lng": "81.1496"
+      }
+    },
+    "phone": "1-770-736-8031 x56442",
+    "website": "hildegard.org",
+    "company": {
+      "name": "Romaguera-Crona",
+      "catchPhrase": "Multi-layered client-server neural-net",
+      "bs": "harness real-time e-markets"
+    }
+  },
+  {
+    "id": 2,
+    "name": "Ervin Howell",
+    "username": "Antonette",
+    "email": "Shanna@melissa.tv",
+    "address": {
+      "street": "Victor Plains",
+      "suite": "Suite 879",
+      "city": "Wisokyburgh",
+      "zipcode": "90566-7771",
+      "geo": {
+        "lat": "-43.9509",
+        "lng": "-34.4618"
+      }
+    },
+    "phone": "010-692-6593 x09125",
+    "website": "anastasia.net",
+    "company": {
+      "name": "Deckow-Crist",
+      "catchPhrase": "Proactive didactic contingency",
+      "bs": "synergize scalable supply-chains"
+    }
+  },
+  {
+    "id": 3,
+    "name": "Clementine Bauch",
+    "username": "Samantha",
+    "email": "Nathan@yesenia.net",
+    "address": {
+      "street": "Douglas Extension",
+      "suite": "Suite 847",
+      "city": "McKenziehaven",
+      "zipcode": "59590-4157",
+      "geo": {
+        "lat": "-68.6102",
+        "lng": "-47.0653"
+      }
+    },
+    "phone": "1-463-123-4447",
+    "website": "ramiro.info",
+    "company": {
+      "name": "Romaguera-Jacobson",
+      "catchPhrase": "Face to face bifurcated interface",
+      "bs": "e-enable strategic applications"
+    }
+  },
+  {
+    "id": 4,
+    "name": "Patricia Lebsack",
+    "username": "Karianne",
+    "email": "Julianne.OConner@kory.org",
+    "address": {
+      "street": "Hoeger Mall",
+      "suite": "Apt. 692",
+      "city": "South Elvis",
+      "zipcode": "53919-4257",
+      "geo": {
+        "lat": "29.4572",
+        "lng": "-164.2990"
+      }
+    },
+    "phone": "493-170-9623 x156",
+    "website": "kale.biz",
+    "company": {
+      "name": "Robel-Corkery",
+      "catchPhrase": "Multi-tiered zero tolerance productivity",
+      "bs": "transition cutting-edge web services"
+    }
+  },
+  {
+    "id": 5,
+    "name": "Chelsey Dietrich",
+    "username": "Kamren",
+    "email": "Lucio_Hettinger@annie.ca",
+    "address": {
+      "street": "Skiles Walks",
+      "suite": "Suite 351",
+      "city": "Roscoeview",
+      "zipcode": "33263",
+      "geo": {
+        "lat": "-31.8129",
+        "lng": "62.5342"
+      }
+    },
+    "phone": "(254)954-1289",
+    "website": "demarco.info",
+    "company": {
+      "name": "Keebler LLC",
+      "catchPhrase": "User-centric fault-tolerant solution",
+      "bs": "revolutionize end-to-end systems"
+    }
+  },
+  {
+    "id": 6,
+    "name": "Mrs. Dennis Schulist",
+    "username": "Leopoldo_Corkery",
+    "email": "Karley_Dach@jasper.info",
+    "address": {
+      "street": "Norberto Crossing",
+      "suite": "Apt. 950",
+      "city": "South Christy",
+      "zipcode": "23505-1337",
+      "geo": {
+        "lat": "-71.4197",
+        "lng": "71.7478"
+      }
+    },
+    "phone": "1-477-935-8478 x6430",
+    "website": "ola.org",
+    "company": {
+      "name": "Considine-Lockman",
+      "catchPhrase": "Synchronised bottom-line interface",
+      "bs": "e-enable innovative applications"
+    }
+  },
+  {
+    "id": 7,
+    "name": "Kurtis Weissnat",
+    "username": "Elwyn.Skiles",
+    "email": "Telly.Hoeger@billy.biz",
+    "address": {
+      "street": "Rex Trail",
+      "suite": "Suite 280",
+      "city": "Howemouth",
+      "zipcode": "58804-1099",
+      "geo": {
+        "lat": "24.8918",
+        "lng": "21.8984"
+      }
+    },
+    "phone": "210.067.6132",
+    "website": "elvis.io",
+    "company": {
+      "name": "Johns Group",
+      "catchPhrase": "Configurable multimedia task-force",
+      "bs": "generate enterprise e-tailers"
+    }
+  },
+  {
+    "id": 8,
+    "name": "Nicholas Runolfsdottir V",
+    "username": "Maxime_Nienow",
+    "email": "Sherwood@rosamond.me",
+    "address": {
+      "street": "Ellsworth Summit",
+      "suite": "Suite 729",
+      "city": "Aliyaview",
+      "zipcode": "45169",
+      "geo": {
+        "lat": "-14.3990",
+        "lng": "-120.7677"
+      }
+    },
+    "phone": "586.493.6943 x140",
+    "website": "jacynthe.com",
+    "company": {
+      "name": "Abernathy Group",
+      "catchPhrase": "Implemented secondary concept",
+      "bs": "e-enable extensible e-tailers"
+    }
+  },
+  {
+    "id": 9,
+    "name": "Glenna Reichert",
+    "username": "Delphine",
+    "email": "Chaim_McDermott@dana.io",
+    "address": {
+      "street": "Dayna Park",
+      "suite": "Suite 449",
+      "city": "Bartholomebury",
+      "zipcode": "76495-3109",
+      "geo": {
+        "lat": "24.6463",
+        "lng": "-168.8889"
+      }
+    },
+    "phone": "(775)976-6794 x41206",
+    "website": "conrad.com",
+    "company": {
+      "name": "Yost and Sons",
+      "catchPhrase": "Switchable contextually-based project",
+      "bs": "aggregate real-time technologies"
+    }
+  },
+  {
+    "id": 10,
+    "name": "Clementina DuBuque",
+    "username": "Moriah.Stanton",
+    "email": "Rey.Padberg@karina.biz",
+    "address": {
+      "street": "Kattie Turnpike",
+      "suite": "Suite 198",
+      "city": "Lebsackbury",
+      "zipcode": "31428-2261",
+      "geo": {
+        "lat": "-38.2386",
+        "lng": "57.2232"
+      }
+    },
+    "phone": "024-648-3804",
+    "website": "ambrose.net",
+    "company": {
+      "name": "Hoeger LLC",
+      "catchPhrase": "Centralized empowering task-force",
+      "bs": "target end-to-end models"
+    }
+  }
+]
\ No newline at end of file
diff --git a/cypress/integration/examples/actions.spec.js b/cypress/integration/examples/actions.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..20e12cc6dffa1703c292c115d63d219530d0ba57
--- /dev/null
+++ b/cypress/integration/examples/actions.spec.js
@@ -0,0 +1,272 @@
+/// <reference types="Cypress" />
+
+context('Actions', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/actions')
+  })
+
+  // https://on.cypress.io/interacting-with-elements
+
+  it('.type() - type into a DOM element', () => {
+    // https://on.cypress.io/type
+    cy.get('.action-email')
+      .type('fake@email.com').should('have.value', 'fake@email.com')
+
+      // .type() with special character sequences
+      .type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
+      .type('{del}{selectall}{backspace}')
+
+      // .type() with key modifiers
+      .type('{alt}{option}') //these are equivalent
+      .type('{ctrl}{control}') //these are equivalent
+      .type('{meta}{command}{cmd}') //these are equivalent
+      .type('{shift}')
+
+      // Delay each keypress by 0.1 sec
+      .type('slow.typing@email.com', { delay: 100 })
+      .should('have.value', 'slow.typing@email.com')
+
+    cy.get('.action-disabled')
+      // Ignore error checking prior to type
+      // like whether the input is visible or disabled
+      .type('disabled error checking', { force: true })
+      .should('have.value', 'disabled error checking')
+  })
+
+  it('.focus() - focus on a DOM element', () => {
+    // https://on.cypress.io/focus
+    cy.get('.action-focus').focus()
+      .should('have.class', 'focus')
+      .prev().should('have.attr', 'style', 'color: orange;')
+  })
+
+  it('.blur() - blur off a DOM element', () => {
+    // https://on.cypress.io/blur
+    cy.get('.action-blur').type('About to blur').blur()
+      .should('have.class', 'error')
+      .prev().should('have.attr', 'style', 'color: red;')
+  })
+
+  it('.clear() - clears an input or textarea element', () => {
+    // https://on.cypress.io/clear
+    cy.get('.action-clear').type('Clear this text')
+      .should('have.value', 'Clear this text')
+      .clear()
+      .should('have.value', '')
+  })
+
+  it('.submit() - submit a form', () => {
+    // https://on.cypress.io/submit
+    cy.get('.action-form')
+      .find('[type="text"]').type('HALFOFF')
+    cy.get('.action-form').submit()
+      .next().should('contain', 'Your form has been submitted!')
+  })
+
+  it('.click() - click on a DOM element', () => {
+    // https://on.cypress.io/click
+    cy.get('.action-btn').click()
+
+    // You can click on 9 specific positions of an element:
+    //  -----------------------------------
+    // | topLeft        top       topRight |
+    // |                                   |
+    // |                                   |
+    // |                                   |
+    // | left          center        right |
+    // |                                   |
+    // |                                   |
+    // |                                   |
+    // | bottomLeft   bottom   bottomRight |
+    //  -----------------------------------
+
+    // clicking in the center of the element is the default
+    cy.get('#action-canvas').click()
+
+    cy.get('#action-canvas').click('topLeft')
+    cy.get('#action-canvas').click('top')
+    cy.get('#action-canvas').click('topRight')
+    cy.get('#action-canvas').click('left')
+    cy.get('#action-canvas').click('right')
+    cy.get('#action-canvas').click('bottomLeft')
+    cy.get('#action-canvas').click('bottom')
+    cy.get('#action-canvas').click('bottomRight')
+
+    // .click() accepts an x and y coordinate
+    // that controls where the click occurs :)
+
+    cy.get('#action-canvas')
+      .click(80, 75) // click 80px on x coord and 75px on y coord
+      .click(170, 75)
+      .click(80, 165)
+      .click(100, 185)
+      .click(125, 190)
+      .click(150, 185)
+      .click(170, 165)
+
+    // click multiple elements by passing multiple: true
+    cy.get('.action-labels>.label').click({ multiple: true })
+
+    // Ignore error checking prior to clicking
+    cy.get('.action-opacity>.btn').click({ force: true })
+  })
+
+  it('.dblclick() - double click on a DOM element', () => {
+    // https://on.cypress.io/dblclick
+
+    // Our app has a listener on 'dblclick' event in our 'scripts.js'
+    // that hides the div and shows an input on double click
+    cy.get('.action-div').dblclick().should('not.be.visible')
+    cy.get('.action-input-hidden').should('be.visible')
+  })
+
+  it('.check() - check a checkbox or radio element', () => {
+    // https://on.cypress.io/check
+
+    // By default, .check() will check all
+    // matching checkbox or radio elements in succession, one after another
+    cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
+      .check().should('be.checked')
+
+    cy.get('.action-radios [type="radio"]').not('[disabled]')
+      .check().should('be.checked')
+
+    // .check() accepts a value argument
+    cy.get('.action-radios [type="radio"]')
+      .check('radio1').should('be.checked')
+
+    // .check() accepts an array of values
+    cy.get('.action-multiple-checkboxes [type="checkbox"]')
+      .check(['checkbox1', 'checkbox2']).should('be.checked')
+
+    // Ignore error checking prior to checking
+    cy.get('.action-checkboxes [disabled]')
+      .check({ force: true }).should('be.checked')
+
+    cy.get('.action-radios [type="radio"]')
+      .check('radio3', { force: true }).should('be.checked')
+  })
+
+  it('.uncheck() - uncheck a checkbox element', () => {
+    // https://on.cypress.io/uncheck
+
+    // By default, .uncheck() will uncheck all matching
+    // checkbox elements in succession, one after another
+    cy.get('.action-check [type="checkbox"]')
+      .not('[disabled]')
+      .uncheck().should('not.be.checked')
+
+    // .uncheck() accepts a value argument
+    cy.get('.action-check [type="checkbox"]')
+      .check('checkbox1')
+      .uncheck('checkbox1').should('not.be.checked')
+
+    // .uncheck() accepts an array of values
+    cy.get('.action-check [type="checkbox"]')
+      .check(['checkbox1', 'checkbox3'])
+      .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
+
+    // Ignore error checking prior to unchecking
+    cy.get('.action-check [disabled]')
+      .uncheck({ force: true }).should('not.be.checked')
+  })
+
+  it('.select() - select an option in a <select> element', () => {
+    // https://on.cypress.io/select
+
+    // Select option(s) with matching text content
+    cy.get('.action-select').select('apples')
+
+    cy.get('.action-select-multiple')
+    .select(['apples', 'oranges', 'bananas'])
+
+    // Select option(s) with matching value
+    cy.get('.action-select').select('fr-bananas')
+
+    cy.get('.action-select-multiple')
+      .select(['fr-apples', 'fr-oranges', 'fr-bananas'])
+  })
+
+  it('.scrollIntoView() - scroll an element into view', () => {
+    // https://on.cypress.io/scrollintoview
+
+    // normally all of these buttons are hidden,
+    // because they're not within
+    // the viewable area of their parent
+    // (we need to scroll to see them)
+    cy.get('#scroll-horizontal button')
+      .should('not.be.visible')
+
+    // scroll the button into view, as if the user had scrolled
+    cy.get('#scroll-horizontal button').scrollIntoView()
+      .should('be.visible')
+
+    cy.get('#scroll-vertical button')
+      .should('not.be.visible')
+
+    // Cypress handles the scroll direction needed
+    cy.get('#scroll-vertical button').scrollIntoView()
+      .should('be.visible')
+
+    cy.get('#scroll-both button')
+      .should('not.be.visible')
+
+    // Cypress knows to scroll to the right and down
+    cy.get('#scroll-both button').scrollIntoView()
+      .should('be.visible')
+  })
+
+  it('.trigger() - trigger an event on a DOM element', () => {
+    // https://on.cypress.io/trigger
+
+    // To interact with a range input (slider)
+    // we need to set its value & trigger the
+    // event to signal it changed
+
+    // Here, we invoke jQuery's val() method to set
+    // the value and trigger the 'change' event
+    cy.get('.trigger-input-range')
+      .invoke('val', 25)
+      .trigger('change')
+      .get('input[type=range]').siblings('p')
+      .should('have.text', '25')
+  })
+
+  it('cy.scrollTo() - scroll the window or element to a position', () => {
+
+    // https://on.cypress.io/scrollTo
+
+    // You can scroll to 9 specific positions of an element:
+    //  -----------------------------------
+    // | topLeft        top       topRight |
+    // |                                   |
+    // |                                   |
+    // |                                   |
+    // | left          center        right |
+    // |                                   |
+    // |                                   |
+    // |                                   |
+    // | bottomLeft   bottom   bottomRight |
+    //  -----------------------------------
+
+    // if you chain .scrollTo() off of cy, we will
+    // scroll the entire window
+    cy.scrollTo('bottom')
+
+    cy.get('#scrollable-horizontal').scrollTo('right')
+
+    // or you can scroll to a specific coordinate:
+    // (x axis, y axis) in pixels
+    cy.get('#scrollable-vertical').scrollTo(250, 250)
+
+    // or you can scroll to a specific percentage
+    // of the (width, height) of the element
+    cy.get('#scrollable-both').scrollTo('75%', '25%')
+
+    // control the easing of the scroll (default is 'swing')
+    cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
+
+    // control the duration of the scroll (in ms)
+    cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
+  })
+})
diff --git a/cypress/integration/examples/aliasing.spec.js b/cypress/integration/examples/aliasing.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..95bac735c44bb5771120dfe74724dcb2b70756b7
--- /dev/null
+++ b/cypress/integration/examples/aliasing.spec.js
@@ -0,0 +1,42 @@
+/// <reference types="Cypress" />
+
+context('Aliasing', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/aliasing')
+  })
+
+  it('.as() - alias a DOM element for later use', () => {
+    // https://on.cypress.io/as
+
+    // Alias a DOM element for use later
+    // We don't have to traverse to the element
+    // later in our code, we reference it with @
+
+    cy.get('.as-table').find('tbody>tr')
+      .first().find('td').first()
+      .find('button').as('firstBtn')
+
+    // when we reference the alias, we place an
+    // @ in front of its name
+    cy.get('@firstBtn').click()
+
+    cy.get('@firstBtn')
+      .should('have.class', 'btn-success')
+      .and('contain', 'Changed')
+  })
+
+  it('.as() - alias a route for later use', () => {
+
+    // Alias the route to wait for its response
+    cy.server()
+    cy.route('GET', 'comments/*').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.network-btn').click()
+
+    // https://on.cypress.io/wait
+    cy.wait('@getComment').its('status').should('eq', 200)
+
+  })
+})
diff --git a/cypress/integration/examples/assertions.spec.js b/cypress/integration/examples/assertions.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..791383b66518a2c89f3f02219d120d025bb15316
--- /dev/null
+++ b/cypress/integration/examples/assertions.spec.js
@@ -0,0 +1,168 @@
+/// <reference types="Cypress" />
+
+context('Assertions', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/assertions')
+  })
+
+  describe('Implicit Assertions', () => {
+    it('.should() - make an assertion about the current subject', () => {
+      // https://on.cypress.io/should
+      cy.get('.assertion-table')
+        .find('tbody tr:last')
+        .should('have.class', 'success')
+        .find('td')
+        .first()
+        // checking the text of the <td> element in various ways
+        .should('have.text', 'Column content')
+        .should('contain', 'Column content')
+        .should('have.html', 'Column content')
+        // chai-jquery uses "is()" to check if element matches selector
+        .should('match', 'td')
+        // to match text content against a regular expression
+        // first need to invoke jQuery method text()
+        // and then match using regular expression
+        .invoke('text')
+        .should('match', /column content/i)
+
+      // a better way to check element's text content against a regular expression
+      // is to use "cy.contains"
+      // https://on.cypress.io/contains
+      cy.get('.assertion-table')
+        .find('tbody tr:last')
+        // finds first <td> element with text content matching regular expression
+        .contains('td', /column content/i)
+        .should('be.visible')
+
+      // for more information about asserting element's text
+      // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
+    })
+
+    it('.and() - chain multiple assertions together', () => {
+      // https://on.cypress.io/and
+      cy.get('.assertions-link')
+        .should('have.class', 'active')
+        .and('have.attr', 'href')
+        .and('include', 'cypress.io')
+    })
+  })
+
+  describe('Explicit Assertions', () => {
+    // https://on.cypress.io/assertions
+    it('expect - make an assertion about a specified subject', () => {
+      // We can use Chai's BDD style assertions
+      expect(true).to.be.true
+      const o = { foo: 'bar' }
+
+      expect(o).to.equal(o)
+      expect(o).to.deep.equal({ foo: 'bar' })
+      // matching text using regular expression
+      expect('FooBar').to.match(/bar$/i)
+    })
+
+    it('pass your own callback function to should()', () => {
+      // Pass a function to should that can have any number
+      // of explicit assertions within it.
+      // The ".should(cb)" function will be retried
+      // automatically until it passes all your explicit assertions or times out.
+      cy.get('.assertions-p')
+        .find('p')
+        .should(($p) => {
+          // https://on.cypress.io/$
+          // return an array of texts from all of the p's
+          // @ts-ignore TS6133 unused variable
+          const texts = $p.map((i, el) => Cypress.$(el).text())
+
+          // jquery map returns jquery object
+          // and .get() convert this to simple array
+          const paragraphs = texts.get()
+
+          // array should have length of 3
+          expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
+
+          // use second argument to expect(...) to provide clear
+          // message with each assertion
+          expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
+            'Some text from first p',
+            'More text from second p',
+            'And even more text from third p',
+          ])
+        })
+    })
+
+    it('finds element by class name regex', () => {
+      cy.get('.docs-header')
+        .find('div')
+        // .should(cb) callback function will be retried
+        .should(($div) => {
+          expect($div).to.have.length(1)
+
+          const className = $div[0].className
+
+          expect(className).to.match(/heading-/)
+        })
+        // .then(cb) callback is not retried,
+        // it either passes or fails
+        .then(($div) => {
+          expect($div, 'text content').to.have.text('Introduction')
+        })
+    })
+
+    it('can throw any error', () => {
+      cy.get('.docs-header')
+        .find('div')
+        .should(($div) => {
+          if ($div.length !== 1) {
+            // you can throw your own errors
+            throw new Error('Did not find 1 element')
+          }
+
+          const className = $div[0].className
+
+          if (!className.match(/heading-/)) {
+            throw new Error(`Could not find class "heading-" in ${className}`)
+          }
+        })
+    })
+
+    it('matches unknown text between two elements', () => {
+      /**
+       * Text from the first element.
+       * @type {string}
+      */
+      let text
+
+      /**
+       * Normalizes passed text,
+       * useful before comparing text with spaces and different capitalization.
+       * @param {string} s Text to normalize
+      */
+      const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
+
+      cy.get('.two-elements')
+        .find('.first')
+        .then(($first) => {
+          // save text from the first element
+          text = normalizeText($first.text())
+        })
+
+      cy.get('.two-elements')
+        .find('.second')
+        .should(($div) => {
+          // we can massage text before comparing
+          const secondText = normalizeText($div.text())
+
+          expect(secondText, 'second text').to.equal(text)
+        })
+    })
+
+    it('assert - assert shape of an object', () => {
+      const person = {
+        name: 'Joe',
+        age: 20,
+      }
+
+      assert.isObject(person, 'value is object')
+    })
+  })
+})
diff --git a/cypress/integration/examples/connectors.spec.js b/cypress/integration/examples/connectors.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..26deb7ade688d1db7cccafdac2dc3b6d5a63509d
--- /dev/null
+++ b/cypress/integration/examples/connectors.spec.js
@@ -0,0 +1,56 @@
+/// <reference types="Cypress" />
+
+context('Connectors', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/connectors')
+  })
+
+  it('.each() - iterate over an array of elements', () => {
+    // https://on.cypress.io/each
+    cy.get('.connectors-each-ul>li')
+      .each(($el, index, $list) => {
+        console.log($el, index, $list)
+      })
+  })
+
+  it('.its() - get properties on the current subject', () => {
+    // https://on.cypress.io/its
+    cy.get('.connectors-its-ul>li')
+      // calls the 'length' property yielding that value
+      .its('length')
+      .should('be.gt', 2)
+  })
+
+  it('.invoke() - invoke a function on the current subject', () => {
+    // our div is hidden in our script.js
+    // $('.connectors-div').hide()
+
+    // https://on.cypress.io/invoke
+    cy.get('.connectors-div').should('be.hidden')
+      // call the jquery method 'show' on the 'div.container'
+      .invoke('show')
+      .should('be.visible')
+  })
+
+  it('.spread() - spread an array as individual args to callback function', () => {
+    // https://on.cypress.io/spread
+    const arr = ['foo', 'bar', 'baz']
+
+    cy.wrap(arr).spread((foo, bar, baz) => {
+      expect(foo).to.eq('foo')
+      expect(bar).to.eq('bar')
+      expect(baz).to.eq('baz')
+    })
+  })
+
+  it('.then() - invoke a callback function with the current subject', () => {
+    // https://on.cypress.io/then
+    cy.get('.connectors-list > li')
+      .then(($lis) => {
+        expect($lis, '3 items').to.have.length(3)
+        expect($lis.eq(0), 'first item').to.contain('Walk the dog')
+        expect($lis.eq(1), 'second item').to.contain('Feed the cat')
+        expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
+      })
+  })
+})
diff --git a/cypress/integration/examples/cookies.spec.js b/cypress/integration/examples/cookies.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb540e95eb0add5bdce16b543d130390efeaf377
--- /dev/null
+++ b/cypress/integration/examples/cookies.spec.js
@@ -0,0 +1,78 @@
+/// <reference types="Cypress" />
+
+context('Cookies', () => {
+  beforeEach(() => {
+    Cypress.Cookies.debug(true)
+
+    cy.visit('https://example.cypress.io/commands/cookies')
+
+    // clear cookies again after visiting to remove
+    // any 3rd party cookies picked up such as cloudflare
+    cy.clearCookies()
+  })
+
+  it('cy.getCookie() - get a browser cookie', () => {
+    // https://on.cypress.io/getcookie
+    cy.get('#getCookie .set-a-cookie').click()
+
+    // cy.getCookie() yields a cookie object
+    cy.getCookie('token').should('have.property', 'value', '123ABC')
+  })
+
+  it('cy.getCookies() - get browser cookies', () => {
+    // https://on.cypress.io/getcookies
+    cy.getCookies().should('be.empty')
+
+    cy.get('#getCookies .set-a-cookie').click()
+
+    // cy.getCookies() yields an array of cookies
+    cy.getCookies().should('have.length', 1).should((cookies) => {
+
+      // each cookie has these properties
+      expect(cookies[0]).to.have.property('name', 'token')
+      expect(cookies[0]).to.have.property('value', '123ABC')
+      expect(cookies[0]).to.have.property('httpOnly', false)
+      expect(cookies[0]).to.have.property('secure', false)
+      expect(cookies[0]).to.have.property('domain')
+      expect(cookies[0]).to.have.property('path')
+    })
+  })
+
+  it('cy.setCookie() - set a browser cookie', () => {
+    // https://on.cypress.io/setcookie
+    cy.getCookies().should('be.empty')
+
+    cy.setCookie('foo', 'bar')
+
+    // cy.getCookie() yields a cookie object
+    cy.getCookie('foo').should('have.property', 'value', 'bar')
+  })
+
+  it('cy.clearCookie() - clear a browser cookie', () => {
+    // https://on.cypress.io/clearcookie
+    cy.getCookie('token').should('be.null')
+
+    cy.get('#clearCookie .set-a-cookie').click()
+
+    cy.getCookie('token').should('have.property', 'value', '123ABC')
+
+    // cy.clearCookies() yields null
+    cy.clearCookie('token').should('be.null')
+
+    cy.getCookie('token').should('be.null')
+  })
+
+  it('cy.clearCookies() - clear browser cookies', () => {
+    // https://on.cypress.io/clearcookies
+    cy.getCookies().should('be.empty')
+
+    cy.get('#clearCookies .set-a-cookie').click()
+
+    cy.getCookies().should('have.length', 1)
+
+    // cy.clearCookies() yields null
+    cy.clearCookies()
+
+    cy.getCookies().should('be.empty')
+  })
+})
diff --git a/cypress/integration/examples/cypress_api.spec.js b/cypress/integration/examples/cypress_api.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e46520cd161be9f3707126e9e84d618c1c14b73
--- /dev/null
+++ b/cypress/integration/examples/cypress_api.spec.js
@@ -0,0 +1,222 @@
+/// <reference types="Cypress" />
+
+context('Cypress.Commands', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  // https://on.cypress.io/custom-commands
+
+  it('.add() - create a custom command', () => {
+    Cypress.Commands.add('console', {
+      prevSubject: true,
+    }, (subject, method) => {
+      // the previous subject is automatically received
+      // and the commands arguments are shifted
+
+      // allow us to change the console method used
+      method = method || 'log'
+
+      // log the subject to the console
+      // @ts-ignore TS7017
+      console[method]('The subject is', subject)
+
+      // whatever we return becomes the new subject
+      // we don't want to change the subject so
+      // we return whatever was passed in
+      return subject
+    })
+
+    // @ts-ignore TS2339
+    cy.get('button').console('info').then(($button) => {
+      // subject is still $button
+    })
+  })
+})
+
+
+context('Cypress.Cookies', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  // https://on.cypress.io/cookies
+  it('.debug() - enable or disable debugging', () => {
+    Cypress.Cookies.debug(true)
+
+    // Cypress will now log in the console when
+    // cookies are set or cleared
+    cy.setCookie('fakeCookie', '123ABC')
+    cy.clearCookie('fakeCookie')
+    cy.setCookie('fakeCookie', '123ABC')
+    cy.clearCookie('fakeCookie')
+    cy.setCookie('fakeCookie', '123ABC')
+  })
+
+  it('.preserveOnce() - preserve cookies by key', () => {
+    // normally cookies are reset after each test
+    cy.getCookie('fakeCookie').should('not.be.ok')
+
+    // preserving a cookie will not clear it when
+    // the next test starts
+    cy.setCookie('lastCookie', '789XYZ')
+    Cypress.Cookies.preserveOnce('lastCookie')
+  })
+
+  it('.defaults() - set defaults for all cookies', () => {
+    // now any cookie with the name 'session_id' will
+    // not be cleared before each new test runs
+    Cypress.Cookies.defaults({
+      whitelist: 'session_id',
+    })
+  })
+})
+
+
+context('Cypress.Server', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  // Permanently override server options for
+  // all instances of cy.server()
+
+  // https://on.cypress.io/cypress-server
+  it('.defaults() - change default config of server', () => {
+    Cypress.Server.defaults({
+      delay: 0,
+      force404: false,
+    })
+  })
+})
+
+context('Cypress.arch', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Get CPU architecture name of underlying OS', () => {
+    // https://on.cypress.io/arch
+    expect(Cypress.arch).to.exist
+  })
+})
+
+context('Cypress.config()', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Get and set configuration options', () => {
+    // https://on.cypress.io/config
+    let myConfig = Cypress.config()
+
+    expect(myConfig).to.have.property('animationDistanceThreshold', 5)
+    expect(myConfig).to.have.property('baseUrl', null)
+    expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
+    expect(myConfig).to.have.property('requestTimeout', 5000)
+    expect(myConfig).to.have.property('responseTimeout', 30000)
+    expect(myConfig).to.have.property('viewportHeight', 660)
+    expect(myConfig).to.have.property('viewportWidth', 1000)
+    expect(myConfig).to.have.property('pageLoadTimeout', 60000)
+    expect(myConfig).to.have.property('waitForAnimations', true)
+
+    expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
+
+    // this will change the config for the rest of your tests!
+    Cypress.config('pageLoadTimeout', 20000)
+
+    expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
+
+    Cypress.config('pageLoadTimeout', 60000)
+  })
+})
+
+context('Cypress.dom', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  // https://on.cypress.io/dom
+  it('.isHidden() - determine if a DOM element is hidden', () => {
+    let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
+    let visibleP = Cypress.$('.dom-p p.visible').get(0)
+
+    // our first paragraph has css class 'hidden'
+    expect(Cypress.dom.isHidden(hiddenP)).to.be.true
+    expect(Cypress.dom.isHidden(visibleP)).to.be.false
+  })
+})
+
+context('Cypress.env()', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  // We can set environment variables for highly dynamic values
+
+  // https://on.cypress.io/environment-variables
+  it('Get environment variables', () => {
+    // https://on.cypress.io/env
+    // set multiple environment variables
+    Cypress.env({
+      host: 'veronica.dev.local',
+      api_server: 'http://localhost:8888/v1/',
+    })
+
+    // get environment variable
+    expect(Cypress.env('host')).to.eq('veronica.dev.local')
+
+    // set environment variable
+    Cypress.env('api_server', 'http://localhost:8888/v2/')
+    expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
+
+    // get all environment variable
+    expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
+    expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
+  })
+})
+
+context('Cypress.log', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Control what is printed to the Command Log', () => {
+    // https://on.cypress.io/cypress-log
+  })
+})
+
+
+context('Cypress.platform', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Get underlying OS name', () => {
+    // https://on.cypress.io/platform
+    expect(Cypress.platform).to.be.exist
+  })
+})
+
+context('Cypress.version', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Get current version of Cypress being run', () => {
+    // https://on.cypress.io/version
+    expect(Cypress.version).to.be.exist
+  })
+})
+
+context('Cypress.spec', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/cypress-api')
+  })
+
+  it('Get current spec information', () => {
+    // https://on.cypress.io/spec
+    // wrap the object so we can inspect it easily by clicking in the command log
+    cy.wrap(Cypress.spec).should('have.keys', ['name', 'relative', 'absolute'])
+  })
+})
diff --git a/cypress/integration/examples/files.spec.js b/cypress/integration/examples/files.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1a9ab2f48eebd9a28183693f59d499dcc0918aa
--- /dev/null
+++ b/cypress/integration/examples/files.spec.js
@@ -0,0 +1,114 @@
+/// <reference types="Cypress" />
+
+/// JSON fixture file can be loaded directly using
+// the built-in JavaScript bundler
+// @ts-ignore
+const requiredExample = require('../../fixtures/example')
+
+context('Files', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/files')
+  })
+
+  beforeEach(() => {
+    // load example.json fixture file and store
+    // in the test context object
+    cy.fixture('example.json').as('example')
+  })
+
+  it('cy.fixture() - load a fixture', () => {
+    // https://on.cypress.io/fixture
+
+    // Instead of writing a response inline you can
+    // use a fixture file's content.
+
+    cy.server()
+    cy.fixture('example.json').as('comment')
+    // when application makes an Ajax request matching "GET comments/*"
+    // Cypress will intercept it and reply with object
+    // from the "comment" alias
+    cy.route('GET', 'comments/*', '@comment').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.fixture-btn').click()
+
+    cy.wait('@getComment').its('responseBody')
+      .should('have.property', 'name')
+      .and('include', 'Using fixtures to represent data')
+
+    // you can also just write the fixture in the route
+    cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.fixture-btn').click()
+
+    cy.wait('@getComment').its('responseBody')
+      .should('have.property', 'name')
+      .and('include', 'Using fixtures to represent data')
+
+    // or write fx to represent fixture
+    // by default it assumes it's .json
+    cy.route('GET', 'comments/*', 'fx:example').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.fixture-btn').click()
+
+    cy.wait('@getComment').its('responseBody')
+      .should('have.property', 'name')
+      .and('include', 'Using fixtures to represent data')
+  })
+
+  it('cy.fixture() or require - load a fixture', function () {
+    // we are inside the "function () { ... }"
+    // callback and can use test context object "this"
+    // "this.example" was loaded in "beforeEach" function callback
+    expect(this.example, 'fixture in the test context')
+      .to.deep.equal(requiredExample)
+
+    // or use "cy.wrap" and "should('deep.equal', ...)" assertion
+    // @ts-ignore
+    cy.wrap(this.example, 'fixture vs require')
+      .should('deep.equal', requiredExample)
+  })
+
+  it('cy.readFile() - read a files contents', () => {
+    // https://on.cypress.io/readfile
+
+    // You can read a file and yield its contents
+    // The filePath is relative to your project's root.
+    cy.readFile('cypress.json').then((json) => {
+      expect(json).to.be.an('object')
+    })
+  })
+
+  it('cy.writeFile() - write to a file', () => {
+    // https://on.cypress.io/writefile
+
+    // You can write to a file
+
+    // Use a response from a request to automatically
+    // generate a fixture file for use later
+    cy.request('https://jsonplaceholder.cypress.io/users')
+      .then((response) => {
+        cy.writeFile('cypress/fixtures/users.json', response.body)
+      })
+    cy.fixture('users').should((users) => {
+      expect(users[0].name).to.exist
+    })
+
+    // JavaScript arrays and objects are stringified
+    // and formatted into text.
+    cy.writeFile('cypress/fixtures/profile.json', {
+      id: 8739,
+      name: 'Jane',
+      email: 'jane@example.com',
+    })
+
+    cy.fixture('profile').should((profile) => {
+      expect(profile.name).to.eq('Jane')
+    })
+  })
+})
diff --git a/cypress/integration/examples/local_storage.spec.js b/cypress/integration/examples/local_storage.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..076b096fc37390fec86f566809d2b1277d119f7e
--- /dev/null
+++ b/cypress/integration/examples/local_storage.spec.js
@@ -0,0 +1,52 @@
+/// <reference types="Cypress" />
+
+context('Local Storage', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/local-storage')
+  })
+  // Although local storage is automatically cleared
+  // in between tests to maintain a clean state
+  // sometimes we need to clear the local storage manually
+
+  it('cy.clearLocalStorage() - clear all data in local storage', () => {
+    // https://on.cypress.io/clearlocalstorage
+    cy.get('.ls-btn').click().should(() => {
+      expect(localStorage.getItem('prop1')).to.eq('red')
+      expect(localStorage.getItem('prop2')).to.eq('blue')
+      expect(localStorage.getItem('prop3')).to.eq('magenta')
+    })
+
+    // clearLocalStorage() yields the localStorage object
+    cy.clearLocalStorage().should((ls) => {
+      expect(ls.getItem('prop1')).to.be.null
+      expect(ls.getItem('prop2')).to.be.null
+      expect(ls.getItem('prop3')).to.be.null
+    })
+
+    // Clear key matching string in Local Storage
+    cy.get('.ls-btn').click().should(() => {
+      expect(localStorage.getItem('prop1')).to.eq('red')
+      expect(localStorage.getItem('prop2')).to.eq('blue')
+      expect(localStorage.getItem('prop3')).to.eq('magenta')
+    })
+
+    cy.clearLocalStorage('prop1').should((ls) => {
+      expect(ls.getItem('prop1')).to.be.null
+      expect(ls.getItem('prop2')).to.eq('blue')
+      expect(ls.getItem('prop3')).to.eq('magenta')
+    })
+
+    // Clear keys matching regex in Local Storage
+    cy.get('.ls-btn').click().should(() => {
+      expect(localStorage.getItem('prop1')).to.eq('red')
+      expect(localStorage.getItem('prop2')).to.eq('blue')
+      expect(localStorage.getItem('prop3')).to.eq('magenta')
+    })
+
+    cy.clearLocalStorage(/prop1|2/).should((ls) => {
+      expect(ls.getItem('prop1')).to.be.null
+      expect(ls.getItem('prop2')).to.be.null
+      expect(ls.getItem('prop3')).to.eq('magenta')
+    })
+  })
+})
diff --git a/cypress/integration/examples/location.spec.js b/cypress/integration/examples/location.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..68e755c101ca8ce06604d79ea8b83107ad5dd8cf
--- /dev/null
+++ b/cypress/integration/examples/location.spec.js
@@ -0,0 +1,32 @@
+/// <reference types="Cypress" />
+
+context('Location', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/location')
+  })
+
+  it('cy.hash() - get the current URL hash', () => {
+    // https://on.cypress.io/hash
+    cy.hash().should('be.empty')
+  })
+
+  it('cy.location() - get window.location', () => {
+    // https://on.cypress.io/location
+    cy.location().should((location) => {
+      expect(location.hash).to.be.empty
+      expect(location.href).to.eq('https://example.cypress.io/commands/location')
+      expect(location.host).to.eq('example.cypress.io')
+      expect(location.hostname).to.eq('example.cypress.io')
+      expect(location.origin).to.eq('https://example.cypress.io')
+      expect(location.pathname).to.eq('/commands/location')
+      expect(location.port).to.eq('')
+      expect(location.protocol).to.eq('https:')
+      expect(location.search).to.be.empty
+    })
+  })
+
+  it('cy.url() - get the current URL', () => {
+    // https://on.cypress.io/url
+    cy.url().should('eq', 'https://example.cypress.io/commands/location')
+  })
+})
diff --git a/cypress/integration/examples/misc.spec.js b/cypress/integration/examples/misc.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..97edd8be0a47c3f18204e01092a7509ba9c1f6e5
--- /dev/null
+++ b/cypress/integration/examples/misc.spec.js
@@ -0,0 +1,83 @@
+/// <reference types="Cypress" />
+
+context('Misc', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/misc')
+  })
+
+  it('.end() - end the command chain', () => {
+    // https://on.cypress.io/end
+
+    // cy.end is useful when you want to end a chain of commands
+    // and force Cypress to re-query from the root element
+    cy.get('.misc-table').within(() => {
+      // ends the current chain and yields null
+      cy.contains('Cheryl').click().end()
+
+      // queries the entire table again
+      cy.contains('Charles').click()
+    })
+  })
+
+  it('cy.exec() - execute a system command', () => {
+    // https://on.cypress.io/exec
+
+    // execute a system command.
+    // so you can take actions necessary for
+    // your test outside the scope of Cypress.
+    cy.exec('echo Jane Lane')
+      .its('stdout').should('contain', 'Jane Lane')
+
+    // we can use Cypress.platform string to
+    // select appropriate command
+    // https://on.cypress/io/platform
+    cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
+
+    if (Cypress.platform === 'win32') {
+      cy.exec('print cypress.json')
+        .its('stderr').should('be.empty')
+    } else {
+      cy.exec('cat cypress.json')
+        .its('stderr').should('be.empty')
+
+      cy.exec('pwd')
+        .its('code').should('eq', 0)
+    }
+  })
+
+  it('cy.focused() - get the DOM element that has focus', () => {
+    // https://on.cypress.io/focused
+    cy.get('.misc-form').find('#name').click()
+    cy.focused().should('have.id', 'name')
+
+    cy.get('.misc-form').find('#description').click()
+    cy.focused().should('have.id', 'description')
+  })
+
+  context('Cypress.Screenshot', function () {
+    it('cy.screenshot() - take a screenshot', () => {
+      // https://on.cypress.io/screenshot
+      cy.screenshot('my-image')
+    })
+
+    it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
+      Cypress.Screenshot.defaults({
+        blackout: ['.foo'],
+        capture: 'viewport',
+        clip: { x: 0, y: 0, width: 200, height: 200 },
+        scale: false,
+        disableTimersAndAnimations: true,
+        screenshotOnRunFailure: true,
+        beforeScreenshot () { },
+        afterScreenshot () { },
+      })
+    })
+  })
+
+  it('cy.wrap() - wrap an object', () => {
+    // https://on.cypress.io/wrap
+    cy.wrap({ foo: 'bar' })
+      .should('have.property', 'foo')
+      .and('include', 'bar')
+  })
+})
diff --git a/cypress/integration/examples/navigation.spec.js b/cypress/integration/examples/navigation.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bbd9d479c8116248ab99efe536e2a35ece8a987d
--- /dev/null
+++ b/cypress/integration/examples/navigation.spec.js
@@ -0,0 +1,56 @@
+/// <reference types="Cypress" />
+
+context('Navigation', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io')
+    cy.get('.navbar-nav').contains('Commands').click()
+    cy.get('.dropdown-menu').contains('Navigation').click()
+  })
+
+  it('cy.go() - go back or forward in the browser\'s history', () => {
+    // https://on.cypress.io/go
+
+    cy.location('pathname').should('include', 'navigation')
+
+    cy.go('back')
+    cy.location('pathname').should('not.include', 'navigation')
+
+    cy.go('forward')
+    cy.location('pathname').should('include', 'navigation')
+
+    // clicking back
+    cy.go(-1)
+    cy.location('pathname').should('not.include', 'navigation')
+
+    // clicking forward
+    cy.go(1)
+    cy.location('pathname').should('include', 'navigation')
+  })
+
+  it('cy.reload() - reload the page', () => {
+    // https://on.cypress.io/reload
+    cy.reload()
+
+    // reload the page without using the cache
+    cy.reload(true)
+  })
+
+  it('cy.visit() - visit a remote url', () => {
+    // https://on.cypress.io/visit
+
+    // Visit any sub-domain of your current domain
+
+    // Pass options to the visit
+    cy.visit('https://example.cypress.io/commands/navigation', {
+      timeout: 50000, // increase total time for the visit to resolve
+      onBeforeLoad (contentWindow) {
+        // contentWindow is the remote page's window object
+        expect(typeof contentWindow === 'object').to.be.true
+      },
+      onLoad (contentWindow) {
+        // contentWindow is the remote page's window object
+        expect(typeof contentWindow === 'object').to.be.true
+      },
+    })
+    })
+})
diff --git a/cypress/integration/examples/network_requests.spec.js b/cypress/integration/examples/network_requests.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..259e9eea570a8e9a62e1279fadf5ddf817c6bb5b
--- /dev/null
+++ b/cypress/integration/examples/network_requests.spec.js
@@ -0,0 +1,140 @@
+/// <reference types="Cypress" />
+
+context('Network Requests', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/network-requests')
+  })
+
+  // Manage AJAX / XHR requests in your app
+
+  it('cy.server() - control behavior of network requests and responses', () => {
+    // https://on.cypress.io/server
+
+    cy.server().should((server) => {
+      // the default options on server
+      // you can override any of these options
+      expect(server.delay).to.eq(0)
+      expect(server.method).to.eq('GET')
+      expect(server.status).to.eq(200)
+      expect(server.headers).to.be.null
+      expect(server.response).to.be.null
+      expect(server.onRequest).to.be.undefined
+      expect(server.onResponse).to.be.undefined
+      expect(server.onAbort).to.be.undefined
+
+      // These options control the server behavior
+      // affecting all requests
+
+      // pass false to disable existing route stubs
+      expect(server.enable).to.be.true
+      // forces requests that don't match your routes to 404
+      expect(server.force404).to.be.false
+      // whitelists requests from ever being logged or stubbed
+      expect(server.whitelist).to.be.a('function')
+    })
+
+    cy.server({
+      method: 'POST',
+      delay: 1000,
+      status: 422,
+      response: {},
+    })
+
+    // any route commands will now inherit the above options
+    // from the server. anything we pass specifically
+    // to route will override the defaults though.
+  })
+
+  it('cy.request() - make an XHR request', () => {
+    // https://on.cypress.io/request
+    cy.request('https://jsonplaceholder.cypress.io/comments')
+      .should((response) => {
+        expect(response.status).to.eq(200)
+        expect(response.body).to.have.length(500)
+        expect(response).to.have.property('headers')
+        expect(response).to.have.property('duration')
+      })
+  })
+
+
+  it('cy.request() - verify response using BDD syntax', () => {
+    cy.request('https://jsonplaceholder.cypress.io/comments')
+    .then((response) => {
+      // https://on.cypress.io/assertions
+      expect(response).property('status').to.equal(200)
+      expect(response).property('body').to.have.length(500)
+      expect(response).to.include.keys('headers', 'duration')
+    })
+  })
+
+  it('cy.request() with query parameters', () => {
+    // will execute request
+    // https://jsonplaceholder.cypress.io/comments?postId=1&id=3
+    cy.request({
+      url: 'https://jsonplaceholder.cypress.io/comments',
+      qs: {
+        postId: 1,
+        id: 3,
+      },
+    })
+    .its('body')
+    .should('be.an', 'array')
+    .and('have.length', 1)
+    .its('0') // yields first element of the array
+    .should('contain', {
+      postId: 1,
+      id: 3,
+    })
+  })
+
+  it('cy.route() - route responses to matching requests', () => {
+    // https://on.cypress.io/route
+
+    let message = 'whoa, this comment does not exist'
+
+    cy.server()
+
+    // Listen to GET to comments/1
+    cy.route('GET', 'comments/*').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.network-btn').click()
+
+    // https://on.cypress.io/wait
+    cy.wait('@getComment').its('status').should('eq', 200)
+
+    // Listen to POST to comments
+    cy.route('POST', '/comments').as('postComment')
+
+    // we have code that posts a comment when
+    // the button is clicked in scripts.js
+    cy.get('.network-post').click()
+    cy.wait('@postComment')
+
+    // get the route
+    cy.get('@postComment').should((xhr) => {
+      expect(xhr.requestBody).to.include('email')
+      expect(xhr.requestHeaders).to.have.property('Content-Type')
+      expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()')
+    })
+
+    // Stub a response to PUT comments/ ****
+    cy.route({
+      method: 'PUT',
+      url: 'comments/*',
+      status: 404,
+      response: { error: message },
+      delay: 500,
+    }).as('putComment')
+
+    // we have code that puts a comment when
+    // the button is clicked in scripts.js
+    cy.get('.network-put').click()
+
+    cy.wait('@putComment')
+
+    // our 404 statusCode logic in scripts.js executed
+    cy.get('.network-put-comment').should('contain', message)
+  })
+})
diff --git a/cypress/integration/examples/querying.spec.js b/cypress/integration/examples/querying.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..484a8ecab8c1aaace5baed5e940b83c80480d1d1
--- /dev/null
+++ b/cypress/integration/examples/querying.spec.js
@@ -0,0 +1,87 @@
+/// <reference types="Cypress" />
+
+context('Querying', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/querying')
+  })
+
+  // The most commonly used query is 'cy.get()', you can
+  // think of this like the '$' in jQuery
+
+  it('cy.get() - query DOM elements', () => {
+    // https://on.cypress.io/get
+
+    cy.get('#query-btn').should('contain', 'Button')
+
+    cy.get('.query-btn').should('contain', 'Button')
+
+    cy.get('#querying .well>button:first').should('contain', 'Button')
+    //              ↲
+    // Use CSS selectors just like jQuery
+
+    cy.get('[data-test-id="test-example"]').should('have.class', 'example')
+
+    // 'cy.get()' yields jQuery object, you can get its attribute
+    // by invoking `.attr()` method
+    cy.get('[data-test-id="test-example"]')
+      .invoke('attr', 'data-test-id')
+      .should('equal', 'test-example')
+
+    // or you can get element's CSS property
+    cy.get('[data-test-id="test-example"]')
+      .invoke('css', 'position')
+      .should('equal', 'static')
+
+    // or use assertions directly during 'cy.get()'
+    // https://on.cypress.io/assertions
+    cy.get('[data-test-id="test-example"]')
+      .should('have.attr', 'data-test-id', 'test-example')
+      .and('have.css', 'position', 'static')
+  })
+
+  it('cy.contains() - query DOM elements with matching content', () => {
+    // https://on.cypress.io/contains
+    cy.get('.query-list')
+      .contains('bananas')
+      .should('have.class', 'third')
+
+    // we can pass a regexp to `.contains()`
+    cy.get('.query-list')
+      .contains(/^b\w+/)
+      .should('have.class', 'third')
+
+    cy.get('.query-list')
+      .contains('apples')
+      .should('have.class', 'first')
+
+    // passing a selector to contains will
+    // yield the selector containing the text
+    cy.get('#querying')
+      .contains('ul', 'oranges')
+      .should('have.class', 'query-list')
+
+    cy.get('.query-button')
+      .contains('Save Form')
+      .should('have.class', 'btn')
+  })
+
+  it('.within() - query DOM elements within a specific element', () => {
+    // https://on.cypress.io/within
+    cy.get('.query-form').within(() => {
+      cy.get('input:first').should('have.attr', 'placeholder', 'Email')
+      cy.get('input:last').should('have.attr', 'placeholder', 'Password')
+    })
+  })
+
+  it('cy.root() - query the root DOM element', () => {
+    // https://on.cypress.io/root
+
+    // By default, root is the document
+    cy.root().should('match', 'html')
+
+    cy.get('.query-ul').within(() => {
+      // In this within, the root is now the ul DOM element
+      cy.root().should('have.class', 'query-ul')
+    })
+  })
+})
diff --git a/cypress/integration/examples/spies_stubs_clocks.spec.js b/cypress/integration/examples/spies_stubs_clocks.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ba939720742ab5e92521406d31bd05edad163ab
--- /dev/null
+++ b/cypress/integration/examples/spies_stubs_clocks.spec.js
@@ -0,0 +1,69 @@
+/// <reference types="Cypress" />
+
+context('Spies, Stubs, and Clock', () => {
+  it('cy.spy() - wrap a method in a spy', () => {
+    // https://on.cypress.io/spy
+    cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+
+    let obj = {
+      foo () {},
+    }
+
+    let spy = cy.spy(obj, 'foo').as('anyArgs')
+
+    obj.foo()
+
+    expect(spy).to.be.called
+  })
+
+  it('cy.stub() - create a stub and/or replace a function with stub', () => {
+    // https://on.cypress.io/stub
+    cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+
+    let obj = {
+      /**
+       * prints both arguments to the console
+       * @param a {string}
+       * @param b {string}
+      */
+      foo (a, b) {
+        console.log('a', a, 'b', b)
+      },
+    }
+
+    let stub = cy.stub(obj, 'foo').as('foo')
+
+    obj.foo('foo', 'bar')
+
+    expect(stub).to.be.called
+  })
+
+  it('cy.clock() - control time in the browser', () => {
+    // https://on.cypress.io/clock
+
+    // create the date in UTC so its always the same
+    // no matter what local timezone the browser is running in
+    let now = new Date(Date.UTC(2017, 2, 14)).getTime()
+
+    cy.clock(now)
+    cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+    cy.get('#clock-div').click()
+      .should('have.text', '1489449600')
+  })
+
+  it('cy.tick() - move time in the browser', () => {
+    // https://on.cypress.io/tick
+
+    // create the date in UTC so its always the same
+    // no matter what local timezone the browser is running in
+    let now = new Date(Date.UTC(2017, 2, 14)).getTime()
+
+    cy.clock(now)
+    cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+    cy.get('#tick-div').click()
+      .should('have.text', '1489449600')
+    cy.tick(10000) // 10 seconds passed
+    cy.get('#tick-div').click()
+      .should('have.text', '1489449610')
+  })
+})
diff --git a/cypress/integration/examples/traversal.spec.js b/cypress/integration/examples/traversal.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..1082eca6cc13b2815b763ca62f7c1402f82367ce
--- /dev/null
+++ b/cypress/integration/examples/traversal.spec.js
@@ -0,0 +1,121 @@
+/// <reference types="Cypress" />
+
+context('Traversal', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/traversal')
+  })
+
+  it('.children() - get child DOM elements', () => {
+    // https://on.cypress.io/children
+    cy.get('.traversal-breadcrumb')
+      .children('.active')
+      .should('contain', 'Data')
+  })
+
+  it('.closest() - get closest ancestor DOM element', () => {
+    // https://on.cypress.io/closest
+    cy.get('.traversal-badge')
+      .closest('ul')
+      .should('have.class', 'list-group')
+  })
+
+  it('.eq() - get a DOM element at a specific index', () => {
+    // https://on.cypress.io/eq
+    cy.get('.traversal-list>li')
+      .eq(1).should('contain', 'siamese')
+  })
+
+  it('.filter() - get DOM elements that match the selector', () => {
+    // https://on.cypress.io/filter
+    cy.get('.traversal-nav>li')
+      .filter('.active').should('contain', 'About')
+  })
+
+  it('.find() - get descendant DOM elements of the selector', () => {
+    // https://on.cypress.io/find
+    cy.get('.traversal-pagination')
+      .find('li').find('a')
+      .should('have.length', 7)
+  })
+
+  it('.first() - get first DOM element', () => {
+    // https://on.cypress.io/first
+    cy.get('.traversal-table td')
+      .first().should('contain', '1')
+  })
+
+  it('.last() - get last DOM element', () => {
+    // https://on.cypress.io/last
+    cy.get('.traversal-buttons .btn')
+      .last().should('contain', 'Submit')
+  })
+
+  it('.next() - get next sibling DOM element', () => {
+    // https://on.cypress.io/next
+    cy.get('.traversal-ul')
+      .contains('apples').next().should('contain', 'oranges')
+  })
+
+  it('.nextAll() - get all next sibling DOM elements', () => {
+    // https://on.cypress.io/nextall
+    cy.get('.traversal-next-all')
+      .contains('oranges')
+      .nextAll().should('have.length', 3)
+  })
+
+  it('.nextUntil() - get next sibling DOM elements until next el', () => {
+    // https://on.cypress.io/nextuntil
+    cy.get('#veggies')
+      .nextUntil('#nuts').should('have.length', 3)
+  })
+
+  it('.not() - remove DOM elements from set of DOM elements', () => {
+    // https://on.cypress.io/not
+    cy.get('.traversal-disabled .btn')
+      .not('[disabled]').should('not.contain', 'Disabled')
+  })
+
+  it('.parent() - get parent DOM element from DOM elements', () => {
+    // https://on.cypress.io/parent
+    cy.get('.traversal-mark')
+      .parent().should('contain', 'Morbi leo risus')
+  })
+
+  it('.parents() - get parent DOM elements from DOM elements', () => {
+    // https://on.cypress.io/parents
+    cy.get('.traversal-cite')
+      .parents().should('match', 'blockquote')
+  })
+
+  it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
+    // https://on.cypress.io/parentsuntil
+    cy.get('.clothes-nav')
+      .find('.active')
+      .parentsUntil('.clothes-nav')
+      .should('have.length', 2)
+  })
+
+  it('.prev() - get previous sibling DOM element', () => {
+    // https://on.cypress.io/prev
+    cy.get('.birds').find('.active')
+      .prev().should('contain', 'Lorikeets')
+  })
+
+  it('.prevAll() - get all previous sibling DOM elements', () => {
+    // https://on.cypress.io/prevAll
+    cy.get('.fruits-list').find('.third')
+      .prevAll().should('have.length', 2)
+  })
+
+  it('.prevUntil() - get all previous sibling DOM elements until el', () => {
+    // https://on.cypress.io/prevUntil
+    cy.get('.foods-list').find('#nuts')
+      .prevUntil('#veggies').should('have.length', 3)
+  })
+
+  it('.siblings() - get all sibling DOM elements', () => {
+    // https://on.cypress.io/siblings
+    cy.get('.traversal-pills .active')
+      .siblings().should('have.length', 2)
+  })
+})
diff --git a/cypress/integration/examples/utilities.spec.js b/cypress/integration/examples/utilities.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..51d153eeb4ede3d7bc98aa733f5b849d3760957a
--- /dev/null
+++ b/cypress/integration/examples/utilities.spec.js
@@ -0,0 +1,133 @@
+/// <reference types="Cypress" />
+
+context('Utilities', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/utilities')
+  })
+
+  it('Cypress._ - call a lodash method', () => {
+    // https://on.cypress.io/_
+    cy.request('https://jsonplaceholder.cypress.io/users')
+      .then((response) => {
+        let ids = Cypress._.chain(response.body).map('id').take(3).value()
+
+        expect(ids).to.deep.eq([1, 2, 3])
+      })
+  })
+
+  it('Cypress.$ - call a jQuery method', () => {
+    // https://on.cypress.io/$
+    let $li = Cypress.$('.utility-jquery li:first')
+
+    cy.wrap($li)
+      .should('not.have.class', 'active')
+      .click()
+      .should('have.class', 'active')
+  })
+
+  it('Cypress.Blob - blob utilities and base64 string conversion', () => {
+    // https://on.cypress.io/blob
+    cy.get('.utility-blob').then(($div) =>
+    // https://github.com/nolanlawson/blob-util#imgSrcToDataURL
+    // get the dataUrl string for the javascript-logo
+      Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
+      .then((dataUrl) => {
+        // create an <img> element and set its src to the dataUrl
+        let img = Cypress.$('<img />', { src: dataUrl })
+
+        // need to explicitly return cy here since we are initially returning
+        // the Cypress.Blob.imgSrcToDataURL promise to our test
+        // append the image
+        $div.append(img)
+
+        cy.get('.utility-blob img').click()
+          .should('have.attr', 'src', dataUrl)
+      }))
+  })
+
+  it('Cypress.minimatch - test out glob patterns against strings', () => {
+    // https://on.cypress.io/minimatch
+    let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
+      matchBase: true,
+    })
+
+    expect(matching, 'matching wildcard').to.be.true
+
+    matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
+      matchBase: true,
+    })
+    expect(matching, 'comments').to.be.false
+
+    // ** matches against all downstream path segments
+    matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
+      matchBase: true,
+    })
+    expect(matching, 'comments').to.be.true
+
+    // whereas * matches only the next path segment
+
+    matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
+      matchBase: false,
+    })
+    expect(matching, 'comments').to.be.false
+  })
+
+
+  it('Cypress.moment() - format or parse dates using a moment method', () => {
+    // https://on.cypress.io/moment
+    const time = Cypress.moment().utc('2014-04-25T19:38:53.196Z').format('h:mm A')
+
+    expect(time).to.be.a('string')
+
+    cy.get('.utility-moment').contains('3:38 PM')
+      .should('have.class', 'badge')
+
+    // the time in the element should be between 3pm and 5pm
+    const start = Cypress.moment('3:00 PM', 'LT')
+    const end = Cypress.moment('5:00 PM', 'LT')
+
+    cy.get('.utility-moment .badge')
+      .should(($el) => {
+        // parse American time like "3:38 PM"
+        const m = Cypress.moment($el.text().trim(), 'LT')
+
+        // display hours + minutes + AM|PM
+        const f = 'h:mm A'
+
+        expect(m.isBetween(start, end),
+          `${m.format(f)} should be between ${start.format(f)} and ${end.format(f)}`).to.be.true
+      })
+  })
+
+
+  it('Cypress.Promise - instantiate a bluebird promise', () => {
+    // https://on.cypress.io/promise
+    let waited = false
+
+    /**
+     * @return Bluebird<string>
+     */
+    function waitOneSecond () {
+      // return a promise that resolves after 1 second
+      // @ts-ignore TS2351 (new Cypress.Promise)
+      return new Cypress.Promise((resolve, reject) => {
+        setTimeout(() => {
+          // set waited to true
+          waited = true
+
+          // resolve with 'foo' string
+          resolve('foo')
+        }, 1000)
+      })
+    }
+
+    cy.then(() =>
+    // return a promise to cy.then() that
+    // is awaited until it resolves
+      // @ts-ignore TS7006
+      waitOneSecond().then((str) => {
+        expect(str).to.eq('foo')
+        expect(waited).to.be.true
+      }))
+  })
+})
diff --git a/cypress/integration/examples/viewport.spec.js b/cypress/integration/examples/viewport.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..711fe74efb11ec01f8add924b67e06b9b30b847e
--- /dev/null
+++ b/cypress/integration/examples/viewport.spec.js
@@ -0,0 +1,59 @@
+/// <reference types="Cypress" />
+
+context('Viewport', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/viewport')
+  })
+
+  it('cy.viewport() - set the viewport size and dimension', () => {
+    // https://on.cypress.io/viewport
+
+    cy.get('#navbar').should('be.visible')
+    cy.viewport(320, 480)
+
+    // the navbar should have collapse since our screen is smaller
+    cy.get('#navbar').should('not.be.visible')
+    cy.get('.navbar-toggle').should('be.visible').click()
+    cy.get('.nav').find('a').should('be.visible')
+
+    // lets see what our app looks like on a super large screen
+    cy.viewport(2999, 2999)
+
+    // cy.viewport() accepts a set of preset sizes
+    // to easily set the screen to a device's width and height
+
+    // We added a cy.wait() between each viewport change so you can see
+    // the change otherwise it is a little too fast to see :)
+
+    cy.viewport('macbook-15')
+    cy.wait(200)
+    cy.viewport('macbook-13')
+    cy.wait(200)
+    cy.viewport('macbook-11')
+    cy.wait(200)
+    cy.viewport('ipad-2')
+    cy.wait(200)
+    cy.viewport('ipad-mini')
+    cy.wait(200)
+    cy.viewport('iphone-6+')
+    cy.wait(200)
+    cy.viewport('iphone-6')
+    cy.wait(200)
+    cy.viewport('iphone-5')
+    cy.wait(200)
+    cy.viewport('iphone-4')
+    cy.wait(200)
+    cy.viewport('iphone-3')
+    cy.wait(200)
+
+    // cy.viewport() accepts an orientation for all presets
+    // the default orientation is 'portrait'
+    cy.viewport('ipad-2', 'portrait')
+    cy.wait(200)
+    cy.viewport('iphone-4', 'landscape')
+    cy.wait(200)
+
+    // The viewport will be reset back to the default dimensions
+    // in between tests (the  default can be set in cypress.json)
+  })
+})
diff --git a/cypress/integration/examples/waiting.spec.js b/cypress/integration/examples/waiting.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e11d9ca9382f606aab855ab9eb5eada67ed70227
--- /dev/null
+++ b/cypress/integration/examples/waiting.spec.js
@@ -0,0 +1,34 @@
+/// <reference types="Cypress" />
+
+context('Waiting', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/waiting')
+  })
+  // BE CAREFUL of adding unnecessary wait times.
+  // https://on.cypress.io/best-practices#Unnecessary-Waiting
+
+  // https://on.cypress.io/wait
+  it('cy.wait() - wait for a specific amount of time', () => {
+    cy.get('.wait-input1').type('Wait 1000ms after typing')
+    cy.wait(1000)
+    cy.get('.wait-input2').type('Wait 1000ms after typing')
+    cy.wait(1000)
+    cy.get('.wait-input3').type('Wait 1000ms after typing')
+    cy.wait(1000)
+  })
+
+  it('cy.wait() - wait for a specific route', () => {
+    cy.server()
+
+    // Listen to GET to comments/1
+    cy.route('GET', 'comments/*').as('getComment')
+
+    // we have code that gets a comment when
+    // the button is clicked in scripts.js
+    cy.get('.network-btn').click()
+
+    // wait for GET comments/1
+    cy.wait('@getComment').its('status').should('eq', 200)
+  })
+
+})
diff --git a/cypress/integration/examples/window.spec.js b/cypress/integration/examples/window.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..00bff9f7fa99eba1cbab8bad523f6acf2a63a0c3
--- /dev/null
+++ b/cypress/integration/examples/window.spec.js
@@ -0,0 +1,22 @@
+/// <reference types="Cypress" />
+
+context('Window', () => {
+  beforeEach(() => {
+    cy.visit('https://example.cypress.io/commands/window')
+  })
+
+  it('cy.window() - get the global window object', () => {
+    // https://on.cypress.io/window
+    cy.window().should('have.property', 'top')
+  })
+
+  it('cy.document() - get the document object', () => {
+    // https://on.cypress.io/document
+    cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
+  })
+
+  it('cy.title() - get the title', () => {
+    // https://on.cypress.io/title
+    cy.title().should('include', 'Kitchen Sink')
+  })
+})
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd170fba6912bb7909ed2ba96c5ff12e8cd3700f
--- /dev/null
+++ b/cypress/plugins/index.js
@@ -0,0 +1,17 @@
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+module.exports = (on, config) => {
+  // `on` is used to hook into various events Cypress emits
+  // `config` is the resolved Cypress config
+}
diff --git a/cypress/screenshots/All Specs/my-image.png b/cypress/screenshots/All Specs/my-image.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc844a4fe801271ac52f4e26451321f27f602a54
Binary files /dev/null and b/cypress/screenshots/All Specs/my-image.png differ
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1f5a772e2bcbd8a318fffcf7ba25e205a92dade
--- /dev/null
+++ b/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This is will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
diff --git a/cypress/support/index.js b/cypress/support/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..d68db96df2697e0835f5c490db0c2cc81673f407
--- /dev/null
+++ b/cypress/support/index.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/package.json b/package.json
index 1bb04c3a5c43f0ac60f0988f147ddbb198b00e75..e57342b32387ad8f86efe66bee9fcaf522b3db33 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "authsome": "^0.1.0",
     "codemirror": "^5.45.0",
     "editoria-api": "^0.1.5",
+    "cypress": "^3.3.0",
     "editoria-common": "^0.1.5",
     "editoria-component-login": "^1.3.0",
     "editoria-component-signup": "^1.1.2",
@@ -176,6 +177,7 @@
     "setupdb": "pubsweet setupdb && npm run seed",
     "start": "pubsweet start",
     "server": "pubsweet start",
+    "cypress": "cypress open",
     "resetdb": "pubsweet setupdb --clobber && npm run seed",
     "start:services": "docker-compose up postgres",
     "seed": "node ./scripts/seedGlobalTeams.js && node ./scripts/seedBookCollection.js",
diff --git a/yarn.lock b/yarn.lock
index 5681ce1fe117e26e4edc5193abf77ba78c89cce3..6fd9d20d6bcf66bbc6b7d14427162d78f0d81d5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -959,6 +959,24 @@
   dependencies:
     find-up "^2.1.0"
 
+"@cypress/listr-verbose-renderer@0.4.1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a"
+  integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=
+  dependencies:
+    chalk "^1.1.3"
+    cli-cursor "^1.0.2"
+    date-fns "^1.27.2"
+    figures "^1.7.0"
+
+"@cypress/xvfb@1.2.4":
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a"
+  integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==
+  dependencies:
+    debug "^3.1.0"
+    lodash.once "^4.1.1"
+
 "@emotion/babel-utils@^0.6.4":
   version "0.6.10"
   resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc"
@@ -2053,6 +2071,11 @@ aproba@^1.0.3, aproba@^1.1.1:
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
   integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
 
+arch@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
+  integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==
+
 archiver-utils@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.0.0.tgz#5639818a8b5d89d0ffc51b72c39283cf4fea14a1"
@@ -2322,6 +2345,13 @@ async@0.2.x, async@~0.2.9:
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
   integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
 
+async@2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
+  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+  dependencies:
+    lodash "^4.17.10"
+
 async@^2.0.0, async@^2.1.4, async@^2.4.1:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
@@ -3746,6 +3776,11 @@ block-stream@*:
   dependencies:
     inherits "~2.0.0"
 
+bluebird@3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
+  integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=
+
 bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.2, bluebird@^3.5.3, bluebird@^3.5.4:
   version "3.5.4"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
@@ -3979,7 +4014,7 @@ buffer-alloc@^1.2.0:
     buffer-alloc-unsafe "^1.1.0"
     buffer-fill "^1.0.0"
 
-buffer-crc32@^0.2.1:
+buffer-crc32@^0.2.1, buffer-crc32@~0.2.3:
   version "0.2.13"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
@@ -4137,7 +4172,7 @@ cached-constructors-x@^1.0.0, cached-constructors-x@^1.0.2:
   resolved "https://registry.yarnpkg.com/cached-constructors-x/-/cached-constructors-x-1.0.2.tgz#d8a7b79b43fdcf13fd861bb763f38b627b0ccf91"
   integrity sha512-7lKwmwXweW6E/31RHAJemLtZPfb2xvcABXknFF4b/dNYv4DbSGTgQHckXLQkNw6BB4HKFYW6mJgsNjADAy1ehw==
 
-cachedir@^1.1.0:
+cachedir@1.3.0, cachedir@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4"
   integrity sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg==
@@ -4329,7 +4364,7 @@ chalk@2.3.2:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.0, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -4368,6 +4403,11 @@ chardet@^0.4.0:
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
   integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
 
+check-more-types@2.24.0:
+  version "2.24.0"
+  resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
+  integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
+
 cheerio@^0.22.0:
   version "0.22.0"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
@@ -4754,6 +4794,11 @@ commander@2.11.x:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
   integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
 
+commander@2.15.1:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
+  integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
+
 commander@2.17.x:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
@@ -4796,6 +4841,11 @@ commitizen@^2.9.6:
     shelljs "0.7.6"
     strip-json-comments "2.0.1"
 
+common-tags@1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
+  integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==
+
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -4849,7 +4899,7 @@ concat-stream@1.6.0:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
-concat-stream@^1.4.10, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0:
+concat-stream@1.6.2, concat-stream@^1.4.10, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -5523,6 +5573,43 @@ cyclist@~0.2.2:
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
   integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
 
+cypress@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.3.0.tgz#d8bc261a8ae2444bde7c9a70e077236f91b897bc"
+  integrity sha512-v/zMPfGe8NdMUUQRx3QyfYbZoIBxJMAFvRzwpTtctq0L01YRFSGQ4exAUbjGvUyhSJk4j6kR29HetIb29TMxXw==
+  dependencies:
+    "@cypress/listr-verbose-renderer" "0.4.1"
+    "@cypress/xvfb" "1.2.4"
+    arch "2.1.1"
+    bluebird "3.5.0"
+    cachedir "1.3.0"
+    chalk "2.4.2"
+    check-more-types "2.24.0"
+    commander "2.15.1"
+    common-tags "1.8.0"
+    debug "3.2.6"
+    execa "0.10.0"
+    executable "4.1.1"
+    extract-zip "1.6.7"
+    fs-extra "4.0.1"
+    getos "3.1.1"
+    glob "7.1.3"
+    is-ci "1.2.1"
+    is-installed-globally "0.1.0"
+    lazy-ass "1.6.0"
+    listr "0.12.0"
+    lodash "4.17.11"
+    log-symbols "2.2.0"
+    minimist "1.2.0"
+    moment "2.24.0"
+    ramda "0.24.1"
+    request "2.88.0"
+    request-progress "0.4.0"
+    supports-color "5.5.0"
+    tmp "0.1.0"
+    url "0.11.0"
+    yauzl "2.10.0"
+
 cz-conventional-changelog@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-2.0.0.tgz#55a979afdfe95e7024879d2a0f5924630170b533"
@@ -5620,6 +5707,13 @@ debug@3.1.0:
   dependencies:
     ms "2.0.0"
 
+debug@3.2.6, debug@^3.0.0, debug@^3.1.0, debug@^3.2.6:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+  dependencies:
+    ms "^2.1.1"
+
 debug@4.1.1, debug@^4.0.1, debug@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@@ -5627,13 +5721,6 @@ debug@4.1.1, debug@^4.0.1, debug@^4.1.0:
   dependencies:
     ms "^2.1.1"
 
-debug@^3.0.0, debug@^3.1.0, debug@^3.2.6:
-  version "3.2.6"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
-  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
-  dependencies:
-    ms "^2.1.1"
-
 decamelize-keys@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
@@ -6860,6 +6947,19 @@ exec-sh@^0.2.0:
   dependencies:
     merge "^1.2.0"
 
+execa@0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
+  integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
+  dependencies:
+    cross-spawn "^6.0.0"
+    get-stream "^3.0.0"
+    is-stream "^1.1.0"
+    npm-run-path "^2.0.0"
+    p-finally "^1.0.0"
+    signal-exit "^3.0.0"
+    strip-eof "^1.0.0"
+
 execa@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -6906,6 +7006,13 @@ execall@^1.0.0:
   dependencies:
     clone-regexp "^1.0.0"
 
+executable@4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
+  integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==
+  dependencies:
+    pify "^2.2.0"
+
 exenv@^1.2.0:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
@@ -7101,6 +7208,16 @@ extract-zip@1.6.6:
     mkdirp "0.5.0"
     yauzl "2.4.1"
 
+extract-zip@1.6.7:
+  version "1.6.7"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
+  integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=
+  dependencies:
+    concat-stream "1.6.2"
+    debug "2.6.9"
+    mkdirp "0.5.1"
+    yauzl "2.4.1"
+
 extsprintf@1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -7190,6 +7307,13 @@ fd-slicer@~1.0.1:
   dependencies:
     pend "~1.2.0"
 
+fd-slicer@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+  integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+  dependencies:
+    pend "~1.2.0"
+
 feature-policy@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
@@ -7582,6 +7706,15 @@ fs-exists-sync@^0.1.0:
   resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
   integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
 
+fs-extra@4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880"
+  integrity sha1-f8DGyJV/mD9X8waiTlud3Y0N2IA=
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^3.0.0"
+    universalify "^0.1.0"
+
 fs-extra@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
@@ -7774,6 +7907,13 @@ getopts@2.2.3:
   resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.3.tgz#11d229775e2ec2067ed8be6fcc39d9b4bf39cf7d"
   integrity sha512-viEcb8TpgeG05+Nqo5EzZ8QR0hxdyrYDp6ZSTZqe2M/h53Bk036NmqG38Vhf5RGirC/Of9Xql+v66B2gp256SQ==
 
+getos@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.1.tgz#967a813cceafee0156b0483f7cffa5b3eff029c5"
+  integrity sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==
+  dependencies:
+    async "2.6.1"
+
 getpass@^0.1.1:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -7850,6 +7990,18 @@ glob@7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+glob@7.1.3:
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
+  integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
 glob@^5.0.3:
   version "5.0.15"
   resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
@@ -8836,7 +8988,7 @@ is-callable@^1.1.3, is-callable@^1.1.4:
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
   integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
 
-is-ci@^1.0.10, is-ci@^1.0.9:
+is-ci@1.2.1, is-ci@^1.0.10, is-ci@^1.0.9:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
   integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
@@ -9024,6 +9176,14 @@ is-index-x@^1.0.0:
     to-number-x "^2.0.0"
     to-string-symbols-supported-x "^1.0.0"
 
+is-installed-globally@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
+  integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
+  dependencies:
+    global-dirs "^0.1.0"
+    is-path-inside "^1.0.0"
+
 is-my-ip-valid@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
@@ -10141,6 +10301,13 @@ jsonfile@^2.1.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonfile@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
+  integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -10309,6 +10476,11 @@ known-css-properties@^0.5.0:
   resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.5.0.tgz#6ff66943ed4a5b55657ee095779a91f4536f8084"
   integrity sha512-LOS0CoS8zcZnB1EjLw4LLqDXw8nvt3AGH5dXLQP3D9O1nLLA+9GC5GnPl5mmF+JiQAtSX4VyZC7KvEtcA4kUtA==
 
+lazy-ass@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
+  integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM=
+
 lazystream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
@@ -10489,6 +10661,20 @@ listr-silent-renderer@^1.1.1:
   resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
   integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
 
+listr-update-renderer@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9"
+  integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=
+  dependencies:
+    chalk "^1.1.3"
+    cli-truncate "^0.2.1"
+    elegant-spinner "^1.0.1"
+    figures "^1.7.0"
+    indent-string "^3.0.0"
+    log-symbols "^1.0.2"
+    log-update "^1.0.2"
+    strip-ansi "^3.0.1"
+
 listr-update-renderer@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7"
@@ -10537,6 +10723,28 @@ listr-verbose-renderer@^0.5.0:
     date-fns "^1.27.2"
     figures "^2.0.0"
 
+listr@0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a"
+  integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo=
+  dependencies:
+    chalk "^1.1.3"
+    cli-truncate "^0.2.1"
+    figures "^1.7.0"
+    indent-string "^2.1.0"
+    is-promise "^2.1.0"
+    is-stream "^1.1.0"
+    listr-silent-renderer "^1.1.1"
+    listr-update-renderer "^0.2.0"
+    listr-verbose-renderer "^0.4.0"
+    log-symbols "^1.0.2"
+    log-update "^1.0.2"
+    ora "^0.2.3"
+    p-map "^1.1.1"
+    rxjs "^5.0.0-beta.11"
+    stream-to-observable "^0.1.0"
+    strip-ansi "^3.0.1"
+
 listr@^0.13.0:
   version "0.13.0"
   resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d"
@@ -10876,7 +11084,7 @@ lodash.omitby@^4.6.0:
   resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791"
   integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E=
 
-lodash.once@^4.0.0:
+lodash.once@^4.0.0, lodash.once@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
   integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
@@ -10976,6 +11184,11 @@ lodash@4.16.4:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.4.tgz#01ce306b9bad1319f2a5528674f88297aeb70127"
   integrity sha1-Ac4wa5utExnypVKGdPiCl663ASc=
 
+lodash@4.17.11, "lodash@4.6.1 || ^4.16.1", lodash@^4, lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10:
+  version "4.17.11"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+
 lodash@4.17.4:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -10986,10 +11199,12 @@ lodash@4.17.5:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
   integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==
 
-"lodash@4.6.1 || ^4.16.1", lodash@^4, lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10:
-  version "4.17.11"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
+  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
+  dependencies:
+    chalk "^2.0.1"
 
 log-symbols@^1.0.2:
   version "1.0.2"
@@ -10998,13 +11213,6 @@ log-symbols@^1.0.2:
   dependencies:
     chalk "^1.0.0"
 
-log-symbols@^2.0.0, log-symbols@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
-  dependencies:
-    chalk "^2.0.1"
-
 log-update-async-hook@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/log-update-async-hook/-/log-update-async-hook-2.0.2.tgz#6eba89dbe67fa12d0b20ac47df7942947af1fcd1"
@@ -11580,7 +11788,7 @@ moment-duration-format@^1.3.0:
   resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-1.3.0.tgz#541771b5f87a049cc65540475d3ad966737d6908"
   integrity sha1-VBdxtfh6BJzGVUBHXTrZZnN9aQg=
 
-moment@^2.10.3, moment@^2.11.2, moment@^2.14.1, moment@^2.18.1, moment@^2.22.1:
+moment@2.24.0, moment@^2.10.3, moment@^2.11.2, moment@^2.14.1, moment@^2.18.1, moment@^2.22.1:
   version "2.24.0"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
   integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
@@ -11792,6 +12000,11 @@ node-emoji@^1.4.1:
   dependencies:
     lodash.toarray "^4.4.0"
 
+node-eta@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/node-eta/-/node-eta-0.1.1.tgz#4066109b39371c761c72b7ebda9a9ea0a5de121f"
+  integrity sha1-QGYQmzk3HHYccrfr2pqeoKXeEh8=
+
 node-fetch@1.6.3:
   version "1.6.3"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
@@ -13606,7 +13819,7 @@ prompt@^1.0.0:
     utile "0.3.x"
     winston "2.1.x"
 
-"prompt@github:flatiron/prompt#1c95d1d8d333b5fbc13fa5f0619f3dcf0d514f87":
+prompt@flatiron/prompt#1c95d1d8d333b5fbc13fa5f0619f3dcf0d514f87:
   version "1.0.0"
   resolved "https://codeload.github.com/flatiron/prompt/tar.gz/1c95d1d8d333b5fbc13fa5f0619f3dcf0d514f87"
   dependencies:
@@ -14131,6 +14344,11 @@ railroad-diagrams@^1.0.0:
   resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
   integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
 
+ramda@0.24.1:
+  version "0.24.1"
+  resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
+  integrity sha1-w7d1UZfzW43DUCIoJixMkd22uFc=
+
 randexp@0.4.6:
   version "0.4.6"
   resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
@@ -15060,6 +15278,14 @@ replicator@^1.0.0:
   resolved "https://registry.yarnpkg.com/replicator/-/replicator-1.0.3.tgz#c0b3ea31e749015bae5d52273f2ae35d541b87ef"
   integrity sha512-WsKsraaM0x0QHy5CtzdgFXUxyowoBhyNkmPqmZShW6h+rOWnyT6Od3zRdTX9r616rAA6kDC9MKQGnSM/CJKfVQ==
 
+request-progress@0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.4.0.tgz#c1954e39086aa85269c5660bcee0142a6a70d7e7"
+  integrity sha1-wZVOOQhqqFJpxWYLzuAUKmpw1+c=
+  dependencies:
+    node-eta "^0.1.1"
+    throttleit "^0.0.2"
+
 request-promise-core@1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346"
@@ -15076,7 +15302,7 @@ request-promise-native@^1.0.5:
     stealthy-require "^1.1.1"
     tough-cookie "^2.3.3"
 
-request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
+request@2.88.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
   version "2.88.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
   integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -15341,7 +15567,7 @@ rx@^4.1.0:
   resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
   integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
 
-rxjs@^5.4.2:
+rxjs@^5.0.0-beta.11, rxjs@^5.4.2:
   version "5.5.12"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
   integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==
@@ -16073,6 +16299,11 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
+stream-to-observable@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
+  integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=
+
 stream-to-observable@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10"
@@ -16401,6 +16632,13 @@ sugarss@^1.0.0:
   dependencies:
     postcss "^6.0.14"
 
+supports-color@5.5.0, supports-color@^5.0.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -16413,13 +16651,6 @@ supports-color@^3.1.2, supports-color@^3.2.3:
   dependencies:
     has-flag "^1.0.0"
 
-supports-color@^5.0.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
-  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
-  dependencies:
-    has-flag "^3.0.0"
-
 supports-color@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
@@ -16798,6 +17029,11 @@ throat@^4.0.0:
   resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
   integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
 
+throttleit@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
+  integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8=
+
 through2@2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -17527,7 +17763,7 @@ url-loader@^0.6.2:
     mime "^1.4.1"
     schema-utils "^0.3.0"
 
-url@^0.11.0:
+url@0.11.0, url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
   integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
@@ -18301,6 +18537,14 @@ yargs@^8.0.1:
     y18n "^3.2.1"
     yargs-parser "^7.0.0"
 
+yauzl@2.10.0:
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+  integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+  dependencies:
+    buffer-crc32 "~0.2.3"
+    fd-slicer "~1.1.0"
+
 yauzl@2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"