Merge pull request #348 from Budibase/feature/add-e2e-tests

Adds integration tests using Cypress

**NOTE**

When adding to CI, we need to add a `yarn build` step. Think that the client library is not being built.
This commit is contained in:
Michael Shanks 2020-06-22 16:16:39 +01:00 committed by GitHub
commit a21fe80da3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 655 additions and 19 deletions

View File

@ -166,6 +166,18 @@ rm -rf ~/.budibase
```
Follow from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation.
### Running tests
#### End-to-end Tests
Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder:
```
yarn test:e2e
```
Or if you are in the builder you can run `yarn cy:test`.
### Other Useful Information

View File

@ -5,6 +5,7 @@
"@rollup/plugin-json": "^4.0.2",
"babel-eslint": "^10.0.3",
"eslint": "^6.8.0",
"eslint-plugin-cypress": "^2.11.1",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-svelte3": "^2.7.3",
"lerna": "3.14.1",
@ -24,10 +25,11 @@
"test": "lerna run test",
"lint": "eslint packages",
"lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
"test:e2e": "lerna run cy:test"
},
"dependencies": {
"@material/icon-button": "4.0.0",
"date-fns": "^2.10.0"
}
}
}

View File

@ -5,4 +5,5 @@ package-lock.json
yarn.lock
release/
dist/
cypress/screenshots
routify

View File

@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:4001/_builder/",
"video": false
}

View File

@ -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"
}

View File

@ -0,0 +1,5 @@
{
"id": 8739,
"name": "Jane",
"email": "jane@example.com"
}

View File

@ -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"
}
}
]

View File

@ -0,0 +1,17 @@
context('Create an Application', () => {
beforeEach(() => {
cy.visit('localhost:4001/_builder')
})
// https://on.cypress.io/interacting-with-elements
it('should create a new application', () => {
// https://on.cypress.io/type
cy.createApp('My Cool App', 'This is a description')
cy.visit('localhost:4001/_builder')
cy.contains('My Cool App').should('exist')
})
})

View File

@ -0,0 +1,53 @@
context('Create Components', () => {
before(() => {
cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type
cy.createApp('Model App', 'Model App Description')
cy.createModel('dog', 'name', 'age')
cy.addRecord('bob', '15')
})
// https://on.cypress.io/interacting-with-elements
it('should add a container', () => {
cy.contains('frontend').click()
cy.get('.switcher > :nth-child(2)').click()
cy.contains('Container').click()
})
it('should add a headline', () => {
cy.addHeadlineComponent('An Amazing headline!')
getIframeBody().contains('An Amazing headline!')
})
it('change the font size of the headline', () => {
cy.contains('Typography').click()
cy.get('input[name="font-size"]')
.type('60px')
cy.contains('Design').click()
getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px')
})
})
const getIframeDocument = () => {
return cy
.get('iframe')
// Cypress yields jQuery element, which has the real
// DOM element under property "0".
// From the real DOM iframe element we can get
// the "document" element, it is stored in "contentDocument" property
// Cypress "its" command can access deep properties using dot notation
// https://on.cypress.io/its
.its('0.contentDocument').should('exist')
}
const getIframeBody = () => {
// get the document
return getIframeDocument()
// automatically retries until body is loaded
.its('body').should('not.be.undefined')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
.then(cy.wrap)
}

View File

@ -0,0 +1,22 @@
context('Create a Model', () => {
before(() => {
cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type
cy.createApp('Model App', 'Model App Description')
})
// https://on.cypress.io/interacting-with-elements
it('should create a new model', () => {
cy.createModel('dog', 'name', 'age')
// Check if model exists
cy.get('.title').should('have.text', 'dog')
})
it('should add a record', () => {
cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob')
})
})

View File

@ -0,0 +1,19 @@
context('Create a User', () => {
before(() => {
cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type
cy.createApp('User App', 'This app is used to test user creation')
})
// https://on.cypress.io/interacting-with-elements
it('should create a user', () => {
// Close Model modal that shows up after creating an app
cy.get('.close').click()
cy.createUser('bbuser', 'test', 'ADMIN')
// Check to make sure user was created!
cy.contains('bbuser').should('have.text', 'bbuser')
})
})

View File

@ -0,0 +1,46 @@
context('Create a workflow', () => {
before(() => {
cy.visit('localhost:4001/_builder')
cy.createApp('Workflow Test App', 'This app is used to test that workflows do in fact work!')
})
// https://on.cypress.io/interacting-with-elements
it('should create a workflow', () => {
cy.createModel('dog', 'name', 'age')
cy.createUser('bbuser', 'test', 'ADMIN')
cy.contains('workflow').click()
cy.get('.new-workflow-button').click()
cy.get('input').type('Add Record')
cy.contains('Save').click()
// Add trigger
cy.get('[data-cy=add-workflow-component]').click()
cy.get('[data-cy=RECORD_SAVED]').click()
cy.get('.budibase__input').select('dog')
// Create action
cy.get('[data-cy=SAVE_RECORD]').click()
cy.get(':nth-child(2) > .budibase__input').type('goodboy')
cy.get(':nth-child(3) > .budibase__input').type('11')
// Save
cy.get('[data-cy=save-workflow-setup]').click()
cy.get('.workflow-button').click()
// Activate Workflow
cy.get('[data-cy=activate-workflow]').click()
})
it('should add record when a new record is added', () => {
cy.contains('backend').click()
cy.addRecord('bob', '15')
cy.contains('goodboy').should('have.text', 'goodboy')
})
})

View File

@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// 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)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@ -0,0 +1,17 @@
// What this script does:
// 1. Removes the old test folder if it exists (.budibase-cypress)
// 2. Initialises using `.budibase-cypress`
// 3. Runs the server using said folder
const rimraf = require("rimraf")
const { join } = require("path")
const homedir = join(require("os").homedir(), ".budibase-cypress")
const init = require("../../cli/src/commands/init/initHandler")
const run = require("../../cli/src/commands/run/runHandler")
rimraf.sync(homedir)
init({ dir: homedir, clientId: "cypress-test" }).then(() => {
delete require.cache[require.resolve("../../server/src/environment")]
run({ dir: homedir })
})

View File

@ -0,0 +1,105 @@
// ***********************************************
// 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 will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add("createApp", (name, description) => {
cy.get(".banner-button")
.click()
.get('input[name="name"]')
.type(name)
.should("have.value", name)
cy.get('textarea[name="description"]')
.type(description)
.should("have.value", description)
cy.contains("Save").click()
})
Cypress.Commands.add("createModel", (modelName, firstField, secondField) => {
// Enter model name
cy.get("[data-cy=Name]")
.click()
.type(modelName)
// Add 'name' field
cy.get("[data-cy=add-new-model-field]").click()
cy.get("[data-cy=Name]")
.click()
.type(firstField)
cy.contains("Save").click()
// Add 'age' field
cy.get("[data-cy=add-new-model-field]").click()
cy.get("[data-cy=Name]")
.click()
.type(secondField)
cy.get("select").select("number")
cy.contains("Save").click()
cy.contains(secondField).should("exist")
// Save model
cy.contains("Save").click()
})
Cypress.Commands.add("addRecord", (firstField, secondField) => {
cy.contains("Create new record").click()
cy.get("[data-cy=name-input]")
.click()
.type(firstField)
cy.get("[data-cy=age-input]")
.click()
.type(secondField)
// Save
cy.contains("Save").click()
})
Cypress.Commands.add("createUser", (username, password, level) => {
// Create User
cy.get(".nav-group-header > .ri-add-line").click()
cy.get("[data-cy=username]").type(username)
cy.get("[data-cy=password]").type(password)
cy.get("[data-cy=accessLevel]").select(level)
// Save
cy.contains("Save").click()
})
Cypress.Commands.add("addHeadlineComponent", text => {
cy.get(".switcher > :nth-child(2)").click()
cy.get("[data-cy=Text]").click()
cy.get("[data-cy=Headline]").click()
cy.get(".tabs > :nth-child(2)").click()
cy.get('input[type="text"]').type(text)
cy.contains("Design").click()
})
Cypress.Commands.add("addButtonComponent", () => {
cy.get(".switcher > :nth-child(2)").click()
cy.get("[data-cy=Button]").click()
})

View File

@ -0,0 +1,3 @@
Cypress.Cookies.defaults({
whitelist: "builder:token",
})

View File

@ -0,0 +1,21 @@
// ***********************************************************
// 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 "./cookies"
import "./commands"
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -9,7 +9,11 @@
"test": "jest",
"test:watch": "jest --watchAll",
"dev:builder": "routify --routify-dir routify -c rollup",
"rollup": "rollup -c -w"
"rollup": "rollup -c -w",
"cy:setup": "node ./cypress/setup.js",
"cy:run": "cypress run",
"cy:open": "cypress open",
"cy:test": "start-server-and-test cy:setup http://localhost:4001/_builder cy:run"
},
"jest": {
"globals": {
@ -35,6 +39,14 @@
},
"transformIgnorePatterns": [
"/node_modules/(?!svelte).+\\.js$"
],
"modulePathIgnorePatterns": [
"<rootDir>/cypress/"
]
},
"eslintConfig": {
"extends": [
"plugin:cypress/recommended"
]
},
"dependencies": {
@ -67,10 +79,12 @@
"@sveltech/routify": "1.7.11",
"babel-jest": "^24.8.0",
"browser-sync": "^2.26.7",
"cypress": "^4.8.0",
"http-proxy-middleware": "^0.19.1",
"jest": "^24.8.0",
"ncp": "^2.0.0",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"rollup": "^1.12.0",
"rollup-plugin-alias": "^1.5.2",
"rollup-plugin-browsersync": "^1.0.0",
@ -83,6 +97,7 @@
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"rollup-plugin-url": "^2.2.2",
"start-server-and-test": "^1.11.0",
"svelte": "3.23.x"
},
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"

View File

@ -2,6 +2,7 @@
import { onMount } from "svelte"
import { buildStyle } from "../../helpers.js"
export let value = ""
export let name = ""
export let textAlign = "left"
export let width = "160px"
export let placeholder = ""
@ -25,6 +26,7 @@
</script>
<input
{name}
class:centerPlaceholder
type="text"
value={displayValue}

View File

@ -13,6 +13,7 @@
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input
data-cy={label}
class="budibase__input"
class:uk-form-danger={hasError}
on:change

View File

@ -53,7 +53,10 @@
</div>
<div class="table-controls">
<span class="label">Fields</span>
<div class="hoverable new-field" on:click={() => (showFieldView = true)}>
<div
data-cy="add-new-model-field"
class="hoverable new-field"
on:click={() => (showFieldView = true)}>
Add new field
</div>
</div>

View File

@ -28,15 +28,26 @@
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Username</label>
<input class="uk-input" type="text" bind:value={username} />
<input
data-cy="username"
class="uk-input"
type="text"
bind:value={username} />
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Password</label>
<input class="uk-input" type="password" bind:value={password} />
<input
data-cy="password"
class="uk-input"
type="password"
bind:value={password} />
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Access Level</label>
<select class="uk-select" bind:value={accessLevelId}>
<select
data-cy="accessLevel"
class="uk-select"
bind:value={accessLevelId}>
<option value="" />
<option value="POWER_USER">Power User</option>
<option value="ADMIN">Admin</option>

View File

@ -32,6 +32,7 @@
{#if type === 'select'}
<select
data-cy="{label}-select"
class={determineClassName(type)}
bind:value
class:uk-form-danger={errors.length > 0}>
@ -41,6 +42,7 @@
</select>
{:else}
<input
data-cy="{label}-input"
class={determineClassName(type)}
class:uk-form-danger={errors.length > 0}
{checked}

View File

@ -7,6 +7,7 @@
<div class="tabs">
{#each categories as category}
<li
data-cy={category.name}
on:click={() => onClick(category)}
class:active={selectedCategory === category}>
{category.name}

View File

@ -3,7 +3,11 @@
export let item
</script>
<div class="item-item" in:fly={{ y: 100, duration: 1000 }} on:click>
<div
data-cy={item.name}
class="item-item"
in:fly={{ y: 100, duration: 1000 }}
on:click>
<div class="item-icon">
<i class={item.icon} />
</div>

View File

@ -38,7 +38,8 @@
{...handlevalueKey(value)}
on:change={val => handleChange(key, val)}
onChange={val => handleChange(key, val)}
{...props} />
{...props}
name={key} />
</div>
</div>

View File

@ -95,7 +95,10 @@
{#if workflowBlock}
<WorkflowBlockSetup {workflowBlock} />
<div class="buttons">
<button class="workflow-button hoverable" on:click={saveWorkflow}>
<button
data-cy="save-workflow-setup"
class="workflow-button hoverable"
on:click={saveWorkflow}>
Save Workflow
</button>
<button

View File

@ -49,6 +49,7 @@
class:highlighted={!workflowLive}
class:hoverable={!workflowLive}
class="play-button hoverable"
data-cy="activate-workflow"
on:click={() => setWorkflowLive(true)}>
<i class="ri-play-fill" />
</button>

View File

@ -15,7 +15,10 @@
}
</script>
<div class="workflow-block hoverable" on:click={addBlockToWorkflow}>
<div
class="workflow-block hoverable"
on:click={addBlockToWorkflow}
data-cy={actionId}>
<div>
<i class={blockDefinition.icon} />
</div>

View File

@ -10,6 +10,7 @@
<header>
<span
data-cy="workflow-list"
class="hoverable workflow-header"
class:selected={selectedTab === 'WORKFLOWS'}
on:click={() => (selectedTab = 'WORKFLOWS')}>
@ -17,6 +18,7 @@
</span>
{#if $workflowStore.currentWorkflow}
<span
data-cy="add-workflow-component"
class="hoverable"
class:selected={selectedTab === 'ADD'}
on:click={() => (selectedTab = 'ADD')}>

View File

@ -7,7 +7,7 @@ const Sqrl = require("squirrelly")
const uuid = require("uuid")
module.exports = opts => {
run(opts)
return run(opts)
}
const run = async opts => {

View File

@ -5,7 +5,6 @@ module.exports = ({ dir }) => {
dir = xPlatHomeDir(dir)
process.env.BUDIBASE_DIR = resolve(dir)
require("dotenv").config({ path: resolve(dir, ".env") })
console.log("dotenv loaded")
// dont make this a variable or top level require
// ti will cause environment module to be loaded prematurely

View File

@ -5,9 +5,6 @@ COUCH_DB_URL={{couchDbUrl}}
# identifies a client database - i.e. group of apps
CLIENT_ID={{clientId}}
# Full access API key for server
ADMIN_SECRET={{adminSecret}}
# used to create cookie hashes
JWT_SECRET={{cookieKey1}}

View File

@ -4,5 +4,4 @@ process.env.NODE_ENV = "jest"
process.env.JWT_SECRET = "test-jwtsecret"
process.env.CLIENT_ID = "test-client-id"
process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
process.env.ADMIN_SECRET = "test-admin-secret"
process.env.LOG_LEVEL = "silent"

View File

@ -3,7 +3,6 @@ module.exports = {
NODE_ENV: process.env.NODE_ENV,
JWT_SECRET: process.env.JWT_SECRET,
BUDIBASE_DIR: process.env.BUDIBASE_DIR,
ADMIN_SECRET: process.env.ADMIN_SECRET,
PORT: process.env.PORT,
COUCH_DB_URL: process.env.COUCH_DB_URL,
SALT_ROUNDS: process.env.SALT_ROUNDS,

View File

@ -1980,6 +1980,13 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-plugin-cypress@^2.11.1:
version "2.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.1.tgz#a945e2774b88211e2c706a059d431e262b5c2862"
integrity sha512-MxMYoReSO5+IZMGgpBZHHSx64zYPSPTpXDwsgW7ChlJTF/sA+obqRbHplxD6sBStE+g4Mi0LCLkG4t9liu//mQ==
dependencies:
globals "^11.12.0"
eslint-plugin-prettier@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
@ -2526,7 +2533,7 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
globals@^11.1.0, globals@^11.12.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==