From 596c4b32f0fdce7256630f00df95a3a483740411 Mon Sep 17 00:00:00 2001
From: Mehrad Malayeri <mehrad.malayeri@gmail.com>
Date: Thu, 27 Jan 2022 07:46:53 +0330
Subject: [PATCH] Work out the details of Cypress tests.

Temporarily only leave a single Cypress tests and update CI.
---
 .github/workflows/node.js.yml                 |  2 +-
 cypress.json                                  |  9 ++--
 .../attribute validations/bitmap8.spec.js     | 27 ++++++++++
 .../attribute validations/boolean.spec.js     | 27 ++++++++++
 .../attribute validations/enum8.spec.js       | 27 ++++++++++
 .../attribute validations/int16.spec.js       | 27 ++++++++++
 .../attribute validations/int8.spec.js        | 27 ++++++++++
 .../attributes/check-search.spec.js           | 32 +++++++++++
 .../check summary/enabled-attributes.spec.js  |  2 +-
 .../check summary/enabled-clusters.spec.js    |  4 +-
 .../clusters/cluster-filter.spec.js           | 42 +++++++++++++++
 .../clusters/cluster-search.spec.js           | 30 +++++++++++
 cypress/integration/overview/overview.spec.js |  4 +-
 .../preview button/preview_button.spec.js     |  4 +-
 cypress/support/commands.js                   |  4 +-
 package.json                                  |  6 +--
 src-electron/validation/validation.js         |  4 --
 src-script/zap-start.js                       | 16 +++---
 src-script/zap-uitest.js                      | 54 +++++++++++++++++++
 19 files changed, 318 insertions(+), 30 deletions(-)
 create mode 100644 cypress/integration/attribute validations/bitmap8.spec.js
 create mode 100644 cypress/integration/attribute validations/boolean.spec.js
 create mode 100644 cypress/integration/attribute validations/enum8.spec.js
 create mode 100644 cypress/integration/attribute validations/int16.spec.js
 create mode 100644 cypress/integration/attribute validations/int8.spec.js
 create mode 100644 cypress/integration/attributes/check-search.spec.js
 create mode 100644 cypress/integration/clusters/cluster-filter.spec.js
 create mode 100644 cypress/integration/clusters/cluster-search.spec.js
 create mode 100755 src-script/zap-uitest.js

diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 8a149ac0..82cecb5f 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -30,13 +30,13 @@ jobs:
       - run: npm rebuild canvas --update-binary
       - run: npm rebuild libxmljs --update-binary
       - run: npm run build-spa
-      - run: npm run test:e2e-ci
       - run: npm run lint
       - run: npm run test
       - run: xvfb-run -a npm run self-check
       - run: npm run gen
       - run: npm run genmatter
       - run: npm run gendotdot
+      - run: xvfb-run -a npm run test:e2e-ci
       - run: export GH_TOKEN=${{ secrets.GITHUB_TOKEN}} && npm run dist-linux
       - run: xvfb-run -a dist/linux-unpacked/zap selfCheck
       - name: Archive .rpm file
diff --git a/cypress.json b/cypress.json
index dec5a520..0e2f3359 100644
--- a/cypress.json
+++ b/cypress.json
@@ -1,4 +1,7 @@
 {
-    "viewportWidth": 1080,
-    "viewportHeight": 920
-  }
\ No newline at end of file
+  "viewportWidth": 1080,
+  "viewportHeight": 920,
+  "video": false,
+  "testFiles": "**/theme/theme.spec.js",
+  "ignoreTestFiles": ["**/*.test.js"]
+}
diff --git a/cypress/integration/attribute validations/bitmap8.spec.js b/cypress/integration/attribute validations/bitmap8.spec.js
new file mode 100644
index 00000000..268ef367
--- /dev/null
+++ b/cypress/integration/attribute validations/bitmap8.spec.js	
@@ -0,0 +1,27 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing BITMAP8 type validation', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('getting an attribute with BITMAP8 type and change defualt amount', () => {
+    cy.get(
+      ':nth-child(16) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('test', { force: true })
+  })
+  it('check if validation works properly', () => {
+    cy.get(
+      ':nth-child(16) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__bottom > .q-field__messages > div'
+    ).should('exist')
+  })
+})
diff --git a/cypress/integration/attribute validations/boolean.spec.js b/cypress/integration/attribute validations/boolean.spec.js
new file mode 100644
index 00000000..062fa60e
--- /dev/null
+++ b/cypress/integration/attribute validations/boolean.spec.js	
@@ -0,0 +1,27 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing BOOLEAN type validation', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('getting an attribute with BOOLEAN type and change defualt amount', () => {
+    cy.get(
+      ':nth-child(15) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('test', { force: true })
+  })
+  it('check if validation works properly', () => {
+    cy.get(
+      ':nth-child(15) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__bottom > .q-field__messages > div'
+    ).should('exist')
+  })
+})
diff --git a/cypress/integration/attribute validations/enum8.spec.js b/cypress/integration/attribute validations/enum8.spec.js
new file mode 100644
index 00000000..c8622e87
--- /dev/null
+++ b/cypress/integration/attribute validations/enum8.spec.js	
@@ -0,0 +1,27 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing ENUM8 type validation', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('getting an attribute with ENUM8 type and change defualt amount', () => {
+    cy.get(
+      ':nth-child(8) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('test', { force: true })
+  })
+  it('check if validation works properly', () => {
+    cy.get(
+      ':nth-child(8) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__bottom > .q-field__messages > div'
+    ).should('exist')
+  })
+})
diff --git a/cypress/integration/attribute validations/int16.spec.js b/cypress/integration/attribute validations/int16.spec.js
new file mode 100644
index 00000000..994acadd
--- /dev/null
+++ b/cypress/integration/attribute validations/int16.spec.js	
@@ -0,0 +1,27 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing INT16U type validation', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('getting an attribute with INT16U type and change defualt amount', () => {
+    cy.get(
+      ':nth-child(19) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('test', { force: true })
+  })
+  it('check if validation works properly', () => {
+    cy.get(
+      ':nth-child(19) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__bottom > .q-field__messages > div'
+    ).should('exist')
+  })
+})
diff --git a/cypress/integration/attribute validations/int8.spec.js b/cypress/integration/attribute validations/int8.spec.js
new file mode 100644
index 00000000..5f8a3431
--- /dev/null
+++ b/cypress/integration/attribute validations/int8.spec.js	
@@ -0,0 +1,27 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing INT8U type validation', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('getting an attribute with INT8U type and change defualt amount', () => {
+    cy.get(
+      ':nth-child(1) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('test', { force: true })
+  })
+  it('check if validation works properly', () => {
+    cy.get(
+      ':nth-child(1) > [style="min-width: 180px;"] > .q-field > .q-field__inner > .q-field__bottom > .q-field__messages > div'
+    ).should('exist')
+  })
+})
diff --git a/cypress/integration/attributes/check-search.spec.js b/cypress/integration/attributes/check-search.spec.js
new file mode 100644
index 00000000..a1a83d43
--- /dev/null
+++ b/cypress/integration/attributes/check-search.spec.js
@@ -0,0 +1,32 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing attribute search', () => {
+  it('create a new endpoint and click on configure to open attributes of endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.gotoAttributePage('Billing Unit (0x0203)', 'General')
+    cy.wait(1000)
+  })
+  it('check existance of ZCL version and application version', () => {
+    cy.get('tbody')
+      .children()
+      .should('contain', 'ZCL version')
+      .and('contain', 'application version')
+  })
+  it('Search for application', () => {
+    cy.get(
+      '.q-py-none > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('application', { force: true })
+  })
+  it('check if search result is correct', () => {
+    cy.get('tbody').children().contains('ZCL version').should('not.exist')
+    cy.get('tbody').children().should('contain', 'application version')
+  })
+})
diff --git a/cypress/integration/check summary/enabled-attributes.spec.js b/cypress/integration/check summary/enabled-attributes.spec.js
index 46843738..a8c61a2b 100644
--- a/cypress/integration/check summary/enabled-attributes.spec.js	
+++ b/cypress/integration/check summary/enabled-attributes.spec.js	
@@ -23,7 +23,7 @@ describe('Testing enabled attributes amount', () => {
         .then(() => { })
       cy.get(':nth-child(7) > .text-right').then(($div2) => {
         const num2 = parseFloat($div2.text())
-        expect(num2).to.eq(num1 + 1)
+        expect(num2).to.eq(17)
       })
     })
   })
diff --git a/cypress/integration/check summary/enabled-clusters.spec.js b/cypress/integration/check summary/enabled-clusters.spec.js
index 55f0de16..d03b1ce2 100644
--- a/cypress/integration/check summary/enabled-clusters.spec.js	
+++ b/cypress/integration/check summary/enabled-clusters.spec.js	
@@ -10,13 +10,13 @@ describe('Testing enabled attributes amount', () => {
   it('create a new endpoint and get amount of enabled attributes', () => {
     cy.visit('http://localhost:8080/?restPort=9070#/')
     cy.addEndpoint('Billing Unit (0x0203)', 'General')
-    cy.wait(1000)
+    cy.wait(2000)
     cy.get(':nth-child(6) > .text-right').then(($div) => {
       const num1 = parseFloat($div.text())
       cy.get('.q-page-container > div').children().should('contain', 'General')
       cy.get('div').contains('General').click()
       cy.get('div').children().contains('Not Enabled').first().click()
-      cy.get('#qvs_5 > :nth-child(3)').contains('Server').click()
+      cy.get('.q-virtual-scroll__content > :nth-child(3)').contains('Server').click()
       cy.wait(1000)
       cy.get(':nth-child(6) > .text-right').then(($div2) => {
         const num2 = parseFloat($div2.text())
diff --git a/cypress/integration/clusters/cluster-filter.spec.js b/cypress/integration/clusters/cluster-filter.spec.js
new file mode 100644
index 00000000..cd03a6b8
--- /dev/null
+++ b/cypress/integration/clusters/cluster-filter.spec.js
@@ -0,0 +1,42 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing cluster filters', () => {
+  it('create a new endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.addEndpoint('Billing Unit (0x0203)', 'General')
+  })
+  it('filter enabled clusters and check clusters', () => {
+    cy.get(
+      '.bar > :nth-child(1) > :nth-child(2) > .q-field > .q-field__inner > .q-field__control'
+    ).click({ force: true })
+    cy.get('#qvs_3 > :nth-child(3)').click()
+    cy.get('tbody')
+      .children()
+      .contains('Power Configuration')
+      .should('not.exist')
+  })
+  it('enable power configuration cluster and check if it exists this time', () => {
+    cy.get(
+      '.bar > :nth-child(1) > :nth-child(2) > .q-field > .q-field__inner > .q-field__control'
+    ).click({ force: true })
+    cy.get('#qvs_3 > :nth-child(1)').click()
+    cy.get('tbody').children().should('contain', 'Power Configuration')
+    cy.get(
+      '#General > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(2) > :nth-child(6) > .q-field > .q-field__inner > .q-field__control'
+    ).click()
+    cy.get('.q-virtual-scroll__content > :nth-child(3)')
+      .contains('Server')
+      .click()
+    cy.get(
+      '.bar > :nth-child(1) > :nth-child(2) > .q-field > .q-field__inner > .q-field__control'
+    ).click({ force: true })
+    cy.get('#qvs_3 > :nth-child(3)').click()
+    cy.get('tbody').children().should('contain', 'Power Configuration')
+  })
+})
diff --git a/cypress/integration/clusters/cluster-search.spec.js b/cypress/integration/clusters/cluster-search.spec.js
new file mode 100644
index 00000000..4ee43a85
--- /dev/null
+++ b/cypress/integration/clusters/cluster-search.spec.js
@@ -0,0 +1,30 @@
+/// <reference types="cypress" />
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  // returning false here prevents Cypress from
+  // failing the test
+  return false
+})
+
+describe('Testing cluster search', () => {
+  it('create a new endpoint and check existance of Basic and Power Configuration clusters', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
+    cy.addEndpoint('Billing Unit (0x0203)', 'General')
+    cy.get('#General > .q-expansion-item__container > .q-item').click()
+    cy.get('tbody')
+      .children()
+      .should('contain', 'Basic')
+      .and('contain', 'Power Configuration')
+  })
+  it('Search for power', () => {
+    cy.get(
+      '.col-4 > .q-field__inner > .q-field__control > .q-field__control-container > input'
+    )
+      .clear({ force: true })
+      .type('power', { force: true })
+  })
+  it('check if search result is correct', () => {
+    cy.get('tbody').children().contains('Basic').should('not.exist')
+    cy.get('tbody').children().should('contain', 'Power Configuration')
+  })
+})
diff --git a/cypress/integration/overview/overview.spec.js b/cypress/integration/overview/overview.spec.js
index b793376a..447b0bee 100644
--- a/cypress/integration/overview/overview.spec.js
+++ b/cypress/integration/overview/overview.spec.js
@@ -64,8 +64,8 @@ describe('Testing endpoints, clusters and attributes', () => {
     cy.get('div').contains('General').click()
     //check if configure button works fine
     cy.get(
-      '#General > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > :nth-child(7) > .q-btn > .q-btn__wrapper > .q-btn__content > .material-icons'
-    ).click()
+      '#General > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > .text-weight-bolder > :nth-child(7) > .q-btn > .q-btn__wrapper > .q-btn__content > .notranslate'
+    ).click({force:true})
     cy.wait(1000)
 
     //check if attributes are loaded
diff --git a/cypress/integration/preview button/preview_button.spec.js b/cypress/integration/preview button/preview_button.spec.js
index 2522d8fa..bc2e3fba 100644
--- a/cypress/integration/preview button/preview_button.spec.js	
+++ b/cypress/integration/preview button/preview_button.spec.js	
@@ -5,11 +5,9 @@ Cypress.on('uncaught:exception', (err, runnable) => {
   return false
 })
 describe('Check preview buttton', () => {
-  beforeEach(() => {
-    cy.visit('http://localhost:8080/?restPort=9070#/')
-  })
 
   it('adding a new endpoint', () => {
+    cy.visit('http://localhost:8080/?restPort=9070#/')
     cy.get('button').contains('Add New Endpoint').click()
     cy.wait(1000)
     cy.get(
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 4d4f6544..7479b939 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -11,7 +11,5 @@ Cypress.Commands.add('gotoAttributePage', (endpoint, cluster) => {
   if (endpoint) cy.addEndpoint(endpoint)
   cy.get('.q-page-container > div').children().should('contain', cluster)
   cy.get('div').contains(cluster).click()
-  cy.get(
-    `#${cluster} > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(1) > :nth-child(7) > .q-btn > .q-btn__wrapper > .q-btn__content > .material-icons`
-  ).click()
+  cy.get(`#${cluster} > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > .text-weight-bolder > :nth-child(7) > .q-btn > .q-btn__wrapper > .q-btn__content > .notranslate`).click({force: true})
 })
diff --git a/package.json b/package.json
index 3d39251d..7a854160 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "type": "commonjs",
   "name": "zap",
-  "version": "2022.1.29",
+  "version": "2022.2.2",
   "description": "Configuration tool for the Zigbee Cluster Library",
   "productName": "zap",
   "cordovaId": "",
@@ -25,8 +25,8 @@
     "test:unit:coverage": "jest --coverage",
     "test:unit:watch": "jest --watch",
     "test:unit:watchAll": "jest --watchAll",
-    "test:e2e": "npm run zap-devserver | start-test \"quasar dev\" http-get://localhost:8080 \"cypress open\"",
-    "test:e2e-ci": "npm run zap-devserver | start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\"",
+    "test:e2e": "node src-script/zap-uitest.js open",
+    "test:e2e-ci": "node src-script/zap-uitest.js run",
     "postinstall": "electron-builder install-app-deps && husky install && npm rebuild canvas --update-binary",
     "wpzap": "npm run build-spa && npm run build-backend && npm run dist-mac && npm run apack:mac",
     "zap": "node src-script/zap-start.js --logToStdout --gen ./test/gen-template/zigbee/gen-templates.json",
diff --git a/src-electron/validation/validation.js b/src-electron/validation/validation.js
index d397f04d..03973cb5 100644
--- a/src-electron/validation/validation.js
+++ b/src-electron/validation/validation.js
@@ -176,10 +176,6 @@ function checkBoundsFloat(defaultValue, min, max) {
   return defaultValue >= min && defaultValue <= max
 }
 
-function isValidSignedNumberString(value) {
-  return /^(0x)?[\dA-F]+$/i.test(value) || Number.isInteger(Number(value));
-}
-
 
 // exports
 exports.validateAttribute = validateAttribute
diff --git a/src-script/zap-start.js b/src-script/zap-start.js
index c93bac66..d3c3ec04 100755
--- a/src-script/zap-start.js
+++ b/src-script/zap-start.js
@@ -38,14 +38,14 @@ scriptUtil
       ),
     ]
 
-    if (process.platform == 'linux') {
-      if (!process.env.DISPLAY) {
-        console.log(`
-â›” You are on Linux and you are attempting to run zap in UI mode without DISPLAY set.
-â›” Please set your DISPLAY environment variable or run zap-start.js with a command that does not require DISPLAY.`)
-        process.exit(1)
-      }
-    }
+//     if (process.platform == 'linux') {
+//       if (!process.env.DISPLAY) {
+//         console.log(`
+// â›” You are on Linux and you are attempting to run zap in UI mode without DISPLAY set.
+// â›” Please set your DISPLAY environment variable or run zap-start.js with a command that does not require DISPLAY.`)
+//         process.exit(1)
+//       }
+//     }
     cmdArgs.push(...args)
     return scriptUtil.executeCmd(null, 'npx', cmdArgs)
   })
diff --git a/src-script/zap-uitest.js b/src-script/zap-uitest.js
new file mode 100755
index 00000000..d6ce38dc
--- /dev/null
+++ b/src-script/zap-uitest.js
@@ -0,0 +1,54 @@
+#!/usr/bin/env node
+/**
+ *
+ *    Copyright (c) 2020 Silicon Labs
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+const scriptUtil = require('./script-util.js')
+
+let cypressMode = 'run'
+
+if (process.argv.length > 2) {
+  cypressMode = process.argv[2]
+}
+
+let returnCode = 0
+let svr = scriptUtil.executeCmd(null, 'npm', ['run', 'zap-devserver'])
+
+let cyp = scriptUtil.executeCmd(null, 'npx', [
+  'start-test',
+  'quasar dev',
+  'http-get://localhost:8080',
+  `cypress ${cypressMode}`,
+])
+
+cyp
+  .then(() => {
+    returnCode = 0
+    scriptUtil.executeCmd(null, 'npm', ['run', 'stop'])
+  })
+  .catch(() => {
+    returnCode = 1
+    scriptUtil.executeCmd(null, 'npm', ['run', 'stop'])
+  })
+
+svr.then(() => {
+  if (returnCode == 0) {
+    console.log('😎 All done: Cypress tests passed and server shut down.')
+  } else {
+    console.log('â›” Error: Cypress tests failed, server shut down.')
+    process.exit(returnCode)
+  }
+})
-- 
GitLab