Update Cypress to v10

pull/2175/head
Jérémy Rialland 2022-06-21 13:29:03 +02:00 committed by Jérémy Rialland
parent 87a21a5882
commit 75fe48af8a
23 changed files with 283 additions and 128 deletions

View File

@ -225,20 +225,20 @@ jobs:
fail-fast: false
matrix:
site: ['fr', 'en']
browser: [electron] # Firefox is very slow…
browser: [chrome] # Firefox is very slow…
viewport: [default]
container: [1, 2, 3, 4]
include:
- site: fr
integrationFolder: mon-entreprise
specPattern: mon-entreprise/**/*.{js,jsx,ts,tsx}
baseUrl: ${{ needs.deploy-context.outputs.fr_url }}
language: fr
- site: fr
integrationFolder: mon-entreprise
specPattern: mon-entreprise/**/*.{js,jsx,ts,tsx}
baseUrl: ${{ needs.deploy-context.outputs.fr_url }}
language: fr
- site: en
integrationFolder: mon-entreprise/english
specPattern: mon-entreprise/english/**/*.{js,jsx,ts,tsx}
baseUrl: ${{ needs.deploy-context.outputs.en_url }}
language: en
exclude:
@ -256,13 +256,13 @@ jobs:
uses: actions/checkout@v3
- uses: ./.github/actions/install
- name: Test e2e mon-entreprise on preview (site=${{ matrix.site }}, browser=${{ matrix.browser}}, viewport=${{ matrix.viewport }})
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v4
with:
install: false
working-directory: site
record: true
tag: ${{ needs.deploy-context.outputs.env-name }}_deploy
config: integrationFolder=cypress/integration/${{ matrix.integrationFolder }},baseUrl=${{ matrix.baseUrl }}${{ matrix.viewport == 'small' && ',viewportHeight=740,viewportWidth=360' || '' }}
config: specPattern=cypress/integration/${{ matrix.specPattern }},baseUrl=${{ matrix.baseUrl }}${{ matrix.viewport == 'small' && ',viewportHeight=740,viewportWidth=360' || '' }}
env: language=${{ matrix.language }}
browser: ${{ matrix.browser }}
headless: true
@ -290,11 +290,11 @@ jobs:
container: [1, 2, 3, 4]
include:
- site: fr
integrationFolder: mon-entreprise
specPattern: mon-entreprise/**/*.{js,jsx,ts,tsx}
baseUrl: ${{ needs.deploy-context.outputs.fr_url }}
language: fr
# - site: en
# integrationFolder: mon-entreprise/english
# specPattern: mon-entreprise/english/**/*.{js,jsx,ts,tsx}
# baseUrl: ${{ needs.deploy-context.outputs.en_url }}
# language: en
# exclude:
@ -312,13 +312,13 @@ jobs:
uses: actions/checkout@v3
- uses: ./.github/actions/install
- name: Test e2e mon-entreprise on production (site=${{ matrix.site }}, browser=${{ matrix.browser}}, viewport=${{ matrix.viewport }})
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v4
with:
install: false
working-directory: site
record: true
tag: ${{ needs.deploy-context.outputs.env-name }}_deploy
config: integrationFolder=cypress/integration/${{ matrix.integrationFolder }},baseUrl=${{ matrix.baseUrl }}${{ matrix.viewport == 'small' && ',viewportHeight=740,viewportWidth=360' || '' }}
config: specPattern=cypress/integration/${{ matrix.specPattern }},baseUrl=${{ matrix.baseUrl }}${{ matrix.viewport == 'small' && ',viewportHeight=740,viewportWidth=360' || '' }}
env: language=${{ matrix.language }}
browser: ${{ matrix.browser }}
headless: true

View File

@ -24,16 +24,16 @@ jobs:
- run: node site/scripts/get-cypress-packages.js | xargs npm i --legacy-peer-deps
- name: Test external integration
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v4
with:
install: false
working-directory: site
record: true
tag: external-integration
config: integrationFolder=cypress/integration/external,baseUrl=https://mon-entreprise.urssaf.fr
config: specPattern=cypress/integration/external/**/*.{js,jsx,ts,tsx},baseUrl=https://mon-entreprise.urssaf.fr
- name: e2e tests with external API calls
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v4
with:
install: false
working-directory: site
@ -43,5 +43,5 @@ jobs:
cypress/integration/mon-entreprise/demande-mobilité.js
record: true
tag: external-mon-entreprise-e2e
config: integrationFolder=cypress/integration/mon-entreprise,baseUrl=https://mon-entreprise.urssaf.fr
config: specPattern=cypress/integration/mon-entreprise/**/*.{js,jsx,ts,tsx},baseUrl=https://mon-entreprise.urssaf.fr
env: language=fr,record_http= # prevent stubbing

15
site/cypress.config.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineConfig } from 'cypress'
export default defineConfig({
projectId: 'jxcngh',
env: {
language: 'fr',
},
chromeWebSecurity: false,
e2e: {
// eslint-disable-next-line
setupNodeEvents(on, config) {},
baseUrl: 'http://localhost:8888',
specPattern: 'cypress/integration/mon-entreprise/**/*.{js,jsx,ts,tsx}',
},
})

View File

@ -1,10 +0,0 @@
{
"projectId": "jxcngh",
"baseUrl": "http://localhost:3000/mon-entreprise",
"env": {
"language": "fr"
},
"integrationFolder": "cypress/integration/mon-entreprise",
"pluginsFile": false,
"chromeWebSecurity": false
}

View File

@ -1 +0,0 @@
{}

View File

@ -1,36 +0,0 @@
const fr = Cypress.env('language') === 'fr'
const testText = (selector, callback) =>
cy.get(`[data-test-id=${selector}]`).should(($span) => {
const displayedText = $span.text().trim().replace(/[\s]/g, ' ')
callback(displayedText)
})
describe('Page covid-19', function () {
if (!fr) {
return
}
before(function () {
return cy.visit(encodeURI('/simulateurs/chômage-partiel'))
})
it('should not crash', function () {
cy.contains('Salaire brut mensuel')
})
it.skip('should display 100% de prise en charge pour un SMIC', function () {
cy.contains('SMIC').click()
testText('comparaison-net', (text) =>
expect(text).to.eq('Soit 100 % du revenu net')
)
testText('comparaison-total', (text) =>
expect(text).to.eq('Soit 0 % du coût habituel')
)
})
it('should display an amount for the prise en charge pour un salaire médian', function () {
cy.contains('salaire médian').click()
testText('comparaison-net', (text) =>
expect(text).to.match(/Soit [\d]{2} % du revenu net/)
)
testText('comparaison-total', (text) =>
expect(text).to.match(/Soit [\d]{1} % du coût habituel/)
)
})
})

View File

@ -0,0 +1,39 @@
import { fr } from '../../support/utils'
describe('Page covid-19', function () {
if (!fr) {
return
}
before(function () {
return cy.visit(encodeURI('/simulateurs/chômage-partiel'))
})
it('should not crash', function () {
cy.contains('Salaire brut mensuel')
})
it('should display 100% de prise en charge pour un SMIC', function () {
cy.contains('SMIC').click()
cy.get('[data-test-id=comparaison-net]').contains(
/Soit 100 % du revenu net/
)
cy.get('[data-test-id=comparaison-total]').contains(
/Soit \d % du coût habituel/
)
})
it('should display an amount for the prise en charge pour un salaire médian', function () {
cy.contains('salaire médian').click()
cy.get('[data-test-id=comparaison-net]').contains(
/Soit [\d]{2} % du revenu net/
)
cy.get('[data-test-id=comparaison-total]').contains(
/Soit [\d]{1} % du coût habituel/
)
})
})

View File

@ -1,4 +1,4 @@
const fr = Cypress.env('language') === 'fr'
import { fr } from '../../support/utils'
const FIXTURES_FOLDER = 'cypress/fixtures'
const DEMANDE_MOBILITE_FIXTURES_FOLDER = `${FIXTURES_FOLDER}/demande-mobilité`
@ -27,6 +27,7 @@ describe(`Formulaire demande mobilité (${
cy.clearLocalStorage() // Try to avoid flaky tests
cy.visit(encodeURI('/gérer/demande-mobilité'))
})
after(function () {
cy.writeInterceptResponses(
pendingRequests,
@ -34,9 +35,18 @@ describe(`Formulaire demande mobilité (${
DEMANDE_MOBILITE_FIXTURES_FOLDER
)
})
it('should allow to select salarié', function () {
cy.intercept({
method: 'GET',
hostname: 'geo.api.gouv.fr',
url: '/communes*',
}).as('communes')
cy.contains('Employeur adhérent au TESE ou au CEA').click()
cy.contains('Informations concernant le salarié')
cy.contains('Êtes-vous adhérent au TESE ou au CEA')
.parent()
.next()
@ -44,8 +54,10 @@ describe(`Formulaire demande mobilité (${
.click()
// "coordonnées" section
cy.contains('Nom').click({ force: true })
cy.focused()
cy.contains('Nom')
.parent()
.click()
.focused()
.type('Deaux')
.tab()
.type('Jean Bernard')
@ -55,20 +67,21 @@ describe(`Formulaire demande mobilité (${
.type('Française')
.tab()
.type('1991-07-25')
cy.contains('Non').click().wait(250)
cy.focused()
.tab()
.type('Pouts', { force: true })
.wait(500)
.type('{enter}')
.wait(500)
cy.tab().type('{downarrow}').wait(500)
cy.focused()
cy.contains('Non')
.click()
.focused()
.tab()
.type('Brest')
.wait(500)
.type('Pouts')
.wait('@communes')
.focused()
cy.contains('65100').type('{enter}').focused().tab().type('{downarrow}')
cy.focused().tab().type('Brest').wait('@communes')
cy.contains('29200')
.type('{enter}')
.focused()
.tab()
.type('3 rue de la Rhumerie')
.tab()
@ -81,9 +94,9 @@ describe(`Formulaire demande mobilité (${
.type('14 chemin des Docks')
.tab()
.type('Bre')
.wait(500)
cy.contains('29200').click()
cy.focused()
cy.contains('29200')
.click()
.focused()
.tab()
.type('Deaux')
.tab()
@ -99,9 +112,11 @@ describe(`Formulaire demande mobilité (${
cy.focused().type('2020-11-06').tab().type('2021-04-09').tab()
cy.focused().type('Argen{enter}')
cy.contains('Agent contractuel').click().wait(250)
cy.focused()
.tab()
cy.contains('Agent contractuel').click()
cy.contains("Nom de l'entreprise")
.parent()
.click()
.focused()
.type('Haldithet Docks')
.tab()
.type('64E45 12-654')
@ -118,16 +133,10 @@ describe(`Formulaire demande mobilité (${
cy.focused().tab().type('{downarrow}{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused()
.tab()
.type('{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused()
.tab()
.type('{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused().tab().type('{downarrow}')
cy.focused().tab()
@ -139,8 +148,7 @@ describe(`Formulaire demande mobilité (${
.next()
.contains('Oui')
.click()
cy.contains("Combien d'ayants droits partiront")
cy.focused().tab().type(1)
cy.contains("Combien d'ayants droits partiront").parent().next().type('1')
cy.contains('Ayant droit n°1')
cy.focused()
.tab()

View File

@ -0,0 +1,19 @@
import { fr } from '../../../support/utils'
// @ts-ignore
import prerenderPaths from '../../../prerender-paths.json'
describe('Test prerender', function () {
const paths = (
prerenderPaths as { 'mon-entreprise': string[]; infrance: string[] }
)[fr ? 'mon-entreprise' : 'infrance']
paths.forEach((path) => {
it(`should show the pre-render of ${fr ? 'fr' : 'en'} ${
path || '/'
}`, function () {
cy.visit(encodeURI(path || '/'), { script: false })
.get('#loading', { timeout: 200 })
.should('not.exist')
})
})
})

View File

@ -0,0 +1,7 @@
import { fr } from '../../../support/utils'
describe('Test sitemap', function () {
it(`should visit sitemap ${fr ? 'fr' : 'en'}`, function () {
cy.request(`/sitemap.${fr ? 'fr' : 'en'}.txt`).should('be.ok')
})
})

View File

@ -1,5 +1,6 @@
describe('Status guide', function () {
const fr = Cypress.env('language') === 'fr'
beforeEach(function () {
cy.visit(fr ? encodeURI('/créer') : '/create')
cy.contains(fr ? 'Trouver le bon statut' : 'Find the right status').click()
@ -12,6 +13,7 @@ describe('Status guide', function () {
cy.contains(fr ? 'Un seul associé' : 'Only one partner').click()
cy.contains(fr ? 'Seul ou à plusieurs' : 'Number of partners')
})
it('should guide thought the SASU status', function () {
cy.get('button')
.contains(fr ? 'Seul' : 'Alone')
@ -19,8 +21,7 @@ describe('Status guide', function () {
cy.get('button')
.contains(fr ? 'Société' : 'Limited liability company')
.click()
// The click fails randomly and unexplicablly on CI
cy.wait(200)
cy.get('button').contains('Assimilé').click()
cy.contains(fr ? 'Créer une SASU' : 'Create a SASU').click()
cy.url().should('match', /\/SASU$/)

View File

@ -33,6 +33,12 @@ describe('Landing page', function () {
FIXTURES_FOLDER
)
cy.intercept({
method: 'GET',
hostname: 'api.recherche-entreprises.fabrique.social.gouv.fr',
url: '/api/v1/search*',
}).as('entreprises')
cy.visit('/')
cy.get(currentCompanyPath).should('not.exist')
@ -41,7 +47,7 @@ describe('Landing page', function () {
cy.get(searchInputPath).invoke('attr', 'type').should('equal', 'search')
cy.get(searchInputPath).focus().type('noima')
cy.wait(100)
cy.wait('@entreprises')
cy.get(searchResultsPath).children().should('have.length', 6)
cy.get(searchResultsPath).children().first().click()

View File

@ -1,7 +1,7 @@
const fr = Cypress.env('language') === 'fr'
import { fr } from '../../support/utils'
describe('Recherche globales', function () {
if (!fr || Cypress.config().baseUrl != 'https://mon-entreprise.urssaf.fr') {
if (!fr) {
return
}
@ -10,10 +10,8 @@ describe('Recherche globales', function () {
cy.contains('Rechercher').click()
cy.wait(30)
cy.focused().should('have.attr', 'type', 'search')
cy.wait(100)
cy.contains('Simulateurs')
.next()
.find('[role="button"]')
@ -22,7 +20,6 @@ describe('Recherche globales', function () {
cy.focused().type('avocat')
cy.wait(100)
cy.contains('Simulateurs')
.next()
.find('[role="button"]')

View File

@ -1,4 +1,4 @@
const fr = Cypress.env('language') === 'fr'
import { fr } from '../../support/utils'
describe('Secondary pages', function () {
if (!fr) {
@ -10,9 +10,9 @@ describe('Secondary pages', function () {
cy.contains('Statistiques détaillées')
})
it.skip('navigate in the news section', function () {
it('navigate in the news section', function () {
cy.visit('/nouveautés')
cy.contains('←').click()
cy.url().should('match', /\/nouveautés\/[^/]*$/)
cy.url({ decode: true }).should('match', /\/nouveautés\/[^/]*$/)
})
})

View File

@ -1,11 +1,13 @@
const fr = Cypress.env('language') === 'fr'
const inputSelector =
'div[aria-labelledby="simulator-legend"] input[inputmode="numeric"]'
import { fr } from '../../support/utils'
describe('Simulateur auto-entrepreneur', function () {
if (!fr) {
return
}
const inputSelector =
'div[aria-labelledby="simulator-legend"] input[inputmode="numeric"]'
before(function () {
return cy.visit('/simulateurs/auto-entrepreneur')
})
@ -17,14 +19,17 @@ describe('Simulateur auto-entrepreneur', function () {
cy.contains('Début 2022').click()
cy.contains('ACRE')
})
it('should not have negative value', function () {
cy.contains('Mensuel').click()
cy.wait(100)
cy.get(inputSelector).first().type('{selectall}5000')
cy.get(inputSelector).each(($input) => {
cy.wrap($input).should(($i) => {
const val = +$i.val().replace(/[\s,.€]/g, '')
expect(val).not.to.be.below(4000)
const val = $i
.val()
.toString()
.replace(/[\s,.€]/g, '')
expect(parseInt(val)).not.to.be.below(4000)
})
})
})

View File

@ -1,9 +1,10 @@
const fr = Cypress.env('language') === 'fr'
import { fr } from '../../support/utils'
describe('Simulateur salarié', function () {
if (!fr) {
return
}
before(function () {
return cy.visit(encodeURI('/simulateurs/salarié'))
})
@ -19,7 +20,6 @@ describe('Simulateur salarié', function () {
.click()
cy.get('div[role="dialog"]').contains('Oui').click()
cy.wait(300)
cy.get('div[role="dialog"]').contains('Continuer').click()
cy.get('div[role="dialog"]').contains('Fermer').click()
})
@ -28,9 +28,11 @@ describe('Simulateur salarié', function () {
cy.get(
'#contrat\\ salarié\\ \\.\\ rémunération\\ \\.\\ brut\\ de\\ base'
).should(($input) => {
expect(+$input.val().replace(/[\s,.€]/g, ''))
.to.be.above(1300)
.and.to.be.below(1600)
const val = $input
.val()
.toString()
.replace(/[\s,.€]/g, '')
expect(parseInt(val)).to.be.above(1300).and.to.be.below(1600)
})
})
@ -42,14 +44,17 @@ describe('Simulateur salarié', function () {
.next()
.find('button')
.click()
cy.focused().type(25)
cy.wait(500)
cy.focused().type('25')
cy.contains('Fermer').click()
cy.get(
'#contrat\\ salarié\\ \\.\\ rémunération\\ \\.\\ net\\ après\\ impôt'
).should(($input) => {
expect(+$input.val().replace(/[\s,.€]/g, '')).to.be.below(1000)
const val = $input
.val()
.toString()
.replace(/[\s,.€]/g, '')
expect(parseInt(val)).to.be.below(1000)
})
})
})

View File

@ -1,4 +0,0 @@
// Support IntelliSense for Cypress
{
"include": ["../../node_modules/cypress", "./integration/**/*.js"]
}

View File

@ -0,0 +1 @@
{"mon-entreprise":["","/créer","/gérer","/simulateurs","/simulateurs/salaire-brut-net","/simulateurs/chômage-partiel","/simulateurs/auto-entrepreneur","/simulateurs/indépendant","/simulateurs/sasu","/simulateurs/artiste-auteur","/iframes/simulateur-embauche","/iframes/pamc"],"infrance":["","/calculators/salary","/iframes/simulateur-embauche"]}

View File

@ -23,12 +23,17 @@
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe) => {
import 'cypress-wait-until'
Cypress.Commands.add('iframe', { prevSubject: ['element'] }, ($iframe) => {
// eslint-disable-next-line
return new Cypress.Promise((resolve) => {
// eslint-disable-next-line
setTimeout(() => resolve($iframe.contents().find('body')), 6000)
})
})
import 'cypress-wait-until'
Cypress.Commands.add(
'setInterceptResponses',
(pendingRequests, responses, hostnames, specFixturesFolder) => {
@ -43,11 +48,13 @@ Cypress.Commands.add(
pendingRequests.add(req.url)
req.on('after:response', (res) => {
pendingRequests.delete(req.url)
// @ts-ignore
// eslint-disable-next-line
responses[res.url] = res.body
})
})
} else if (stubFixtures) {
const urlOfFilepath = (filename) => {
const urlOfFilepath = (filename: string) => {
return atob(filename.slice(0, -'.json'.length))
}
cy.exec(`find ${specFixturesFolder} -type f`)
@ -66,6 +73,7 @@ Cypress.Commands.add(
}
}
)
Cypress.Commands.add(
'writeInterceptResponses',
(pendingRequests, responses, specFixturesFolder) => {
@ -75,9 +83,11 @@ Cypress.Commands.add(
// We need to wait on all catched requests to be fulfilled and recorded,
// otherwise the stubbed cy run might error when a request is not stubbed.
// Caveat: we assume request.url to be unique amongst recorded requests.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
cy.waitUntil(() => pendingRequests.size === 0)
Object.keys(responses).map((url) => {
if (responses[url] === undefined) return
Object.keys(responses).forEach((url) => {
if (responses[url] === undefined) return undefined
cy.writeFile(
`${specFixturesFolder}/${btoa(url)}.json`,
JSON.stringify(responses[url], null, 2)
@ -86,3 +96,85 @@ Cypress.Commands.add(
}
}
)
/**
* Add option to disable javascript with { script: false }
*/
Cypress.Commands.overwrite('visit', (orig, url, options = {}) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const window = cy.state('window') as Cypress.AUTWindow
const parentDocument = window.parent.document
const iframe = parentDocument.querySelector('main iframe')
if (!iframe) {
// @ts-ignore
return orig(url, options)
}
if (options.script === false) {
if (Cypress.config('chromeWebSecurity') !== false) {
throw new TypeError(
"When you disable script you also have to set 'chromeWebSecurity' in your config to 'false'"
)
}
// @ts-ignore
iframe.sandbox = ''
} else {
// In case it was added by a visit before, the attribute has to be removed from the iframe
iframe.removeAttribute('sandbox')
}
// @ts-ignore
return orig(url, options)
})
// Types
/// <reference types="cypress" />
declare global {
/* eslint-disable no-unused-vars */
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* @param pendingRequests
* @param responses
* @param hostnames
* @param specFixturesFolder
*/
setInterceptResponses(
pendingRequests: Set<unknown>,
responses: Record<string, unknown>,
hostnames: string[],
specFixturesFolder: string
): Chainable<Element>
/**
* @param pendingRequests
* @param responses
* @param specFixturesFolder
*/
writeInterceptResponses(
pendingRequests: Set<unknown>,
responses: Record<string, unknown>,
specFixturesFolder: string
): Chainable<Element>
/**
*/
iframe(): Chainable<Element>
visit(
url: string,
options?: Partial<Cypress.VisitOptions> & { script?: boolean }
): Chainable<Cypress.AUTWindow>
visit(
options: Partial<Cypress.VisitOptions> & {
url: string
script?: boolean
}
): Chainable<Cypress.AUTWindow>
}
}
/* eslint-enable no-unused-vars */
}

View File

@ -0,0 +1 @@
export const fr = Cypress.env('language') === 'fr'

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"module": "ESNext",
"lib": ["es5", "dom"],
"types": ["cypress", "cypress-plugin-tab/src", "node"],
"allowSyntheticDefaultImports": true
},
"include": ["**/*.ts", "../cypress.config.ts"]
}

View File

@ -32,8 +32,8 @@
"preview:infrance": "sed 's|:SITE_EN||g' | sed 's|:API_URL|http://localhost:3004|g' netlify.toml > dist/netlify.toml && cd dist && npx netlify-cli dev -d ./ -p 8889",
"typecheck:watch": "tsc --skipLibCheck --noEmit --watch",
"test": "vitest",
"test:dev-e2e:mon-entreprise": "cypress open --browser chromium",
"test:dev-e2e:mycompanyinfrance": "cypress open --browser chromium --config baseUrl=http://localhost:8080/infrance,integrationFolder=cypress/integration/mon-entreprise/english --env language=en",
"test:dev-e2e:mon-entreprise": "cypress open --e2e",
"test:dev-e2e:mycompanyinfrance": "cypress open --e2e --config \"baseUrl=http://localhost:8889,specPattern=cypress/integration/mon-entreprise/english/**/*.{js,jsx,ts,tsx}\" --env language=en",
"test:record-http-calls:mon-entreprise": "cypress run --env record_http=",
"algolia:update": "node --loader ts-node/esm scripts/search/update-data.ts",
"algolia:clean": "node scripts/search/clean.js",
@ -133,7 +133,7 @@
"@types/styled-components": "^5.1.24",
"@vitejs/plugin-legacy": "^1.8.2",
"@vitejs/plugin-react": "^1.3.2",
"cypress": "^9.5.4",
"cypress": "^10.1.0",
"cypress-plugin-tab": "^1.0.5",
"cypress-wait-until": "^1.7.2",
"dotenv": "=8.1.0",