Prepare a migration from CodeceptJS to Cypress, making the best of the 2 UAT testing frameworks for an webapplication

After, a getaway in the NFT’s universe with Python, Streamlit, it was probably time to return to some real mediocre issues that often animate a professional life!
I started seriously working with Cypress, another UAT (User Acceptance Testing) testing framework. Same exploration like I did with another UAT (User Acceptance Testing) testing framework named CodeceptJS.

You can grab the source for this post and some other resource on my GitHub account: Code for migration_codeceptjs_to_cypress_write_javascript_class

Migration issues from CodeceptJS to Cypress

With CodeceptJS, as PO, I managed to create an E2E suite with more than a hundred tests that allow an automatic and almost secure acceptance testing for the application. So, migrate the all stuff to Cypress raises questions… Here is my interrogations.

  1. How to migrate this CodeceptJS E2E suite to Cypress preserving whatsoever most of the work already done? Briefly, the main question remains how to make the best of your legacy code?
  2. How can you ensure, both in Cypress or in CodeceptJS, that the testing code is correct when you write a JavaScript Function or a Class without waiting that the test finished? So, how can I fasten the testing writing process?
  3. Last, more personal, can I apply the good practices learned in Python to JavaScript by writing Classes for instance.

These are the questions that I wanted to answer during this migration.

Lessons from this migration

Well, if you replace I. by cy. and you are almost good 🙂 No kidding but thanks to Node, the principles and syntax are not so different from one framework to another so the logic and even the conversion can be easily made.

As the logic was the same, with this migration, the challenge was more to be a cut above with Cypress from the very first E2E testing suite made with CodeceptJS! Unfortunately, du to security reason, I cannot release the code both from my E2E suite made in Cypress or in CodeceptJS. I just show a very small piece of the same code converted just to give a glimpse on what has been done.

Extract in CodeceptJS’ syntax

I.say('\n--- TESTING VIDEO', 'red');'//*[@id="acces-panel-collapse"]/div/a[11]');
// Fill title
I.fillField('//*[@id="video_title"]', 'Video '+globalVariables.startDateString+' '+globalVariables.RandomString+''); 
I.fillField('//*[@id="video_youtubeId"]', ''+globalValues.VIDEO_YOUTUBE_ID_TESTING+''); 
I.fillField('//*[@id="video_url"]', ''+globalValues.VIDEO_MP4_URL+''); 
// Add a media''+selectImageDefault+'');
I.say('--- Add a media image '+globalVariables.RandomImageInsertSmall+'');
/* code omitted for brevity */

The same extract converted in Cypress’ syntax

cy.log('\n--- TESTING VIDEO')
// Fill title
cy.xpath('//*[@id="video_title"]').type('Video '+globalVariables.startDateString+' '+globalVariables.RandomString+'')
cy.xpath('//*[@id="video_url"]', '').type(''+globalValues.VIDEO_MP4_URL+'') 
// Add a media
cy.get(''+selectImageDefault+'').click({force: true})
cy.log('--- Add a media image '+globalVariables.RandomImageInsertSmall+'')
/* code omitted for brevity */

My todolist for migration to CodeceptJS to Cypress

So, here is in extenso the roadmap/todolist that I wrote to myself with the asserted objective to rely as much as possible on the existing tests written with CodeceptJS (logic, functions, variables…):

  • To facilitate migration and do the less work as possible to migrate from CodeceptJS to Cypress. So, managing the minimum of files by making Class, Functions, Variables as much as possible.
  • To make the tests more complete, agnostic and resilient while remaining USABLE! Basically, less brittleness more robustness in the tests so that pass regardless the staging environment or any external parameters… etc. It was also the occasion not to lecture on how to write tests but paradoxically balance the abstraction level. Indeed, to abstract is to simplify but the greater the degree of abstraction is, the more complex the formulation is, and vice versa.
  • To ensure that all E2E tests pass indifferently in the runner (PO) or in the console (DEV) and ultimately that they must be used to support a GO in production.

This shortlist is based on the advantages and disadvantages encountered with CodeceptJS. Accordingly, the roadmap/todolist was as follows:

  • TASK_1 :: Check main navigation with a maximum of abstraction
  • TASK_2 :: Create function for each type of content (article, edition, …etc).
  • TASK_3 :: Use as much as possible env file to be able to launch the test on any combination for brand/lang or set of parameters e.g cypress.json or cypress.env.json, [brand-lang].conf.env.json
  • TASK_4 :: Generate reports for cypress (mochawesome, allure)
  • TASK_5 :: Leveraging on node and declare scripts commands in package.json to shortcut actions
  • TASK_6 :: Using GITBASH on windows for Automation (a more personal and practical issue)

Even thought these tasks may enter in conflict with the Best Practices
recommended by Cypress: e.g. Unnecessary Waiting

First conclusions on this migration

For any evolution, I finally manage a minimum. It means manipulating almost only two files. I frequently modify them for the sake of the testing and the web application quality!

Most of the time I am manipulating only 2 files. Less is more.

Concretely, what are the immediate benefices:

  1. Testing information is no disseminated in every test’s files.
  2. The file structure test is always the same as I am using naming conventions everywhere both from filenames to keywords.
  3. Duplication and reuse are easy as everything is in the file commands.js. So, I just sample and slightly change a function when I start writing a new test. My own work is a source of inspiration.
  4. All elements (selector, xpath, typing text, images to upload… etc.) have been withdraw from the testing files… so tests are not brittle even though there are changes in the HTML or the CSS. So, you avoid the Anti-Patterns pitfall! See this post to get more details

All specifications depending on Brand/Lang are externalised in a config file.
node_test_cypress/cypress_try_3/config/F24_AR.conf.env.json... etc

All functions are located and externalised in the single file commands.js

// cy.BachEndUpLight()
Cypress.Commands.add('BachEndUpLight', () => {
/* code omitted for brevity */
// cy.BachDebug()
Cypress.Commands.add('BachDebug', () => {
cy.log(' -- BachDebug functions --')
cy.log(' -- doRandomString () :: '+doRandomString()+' --')
cy.log(' -- doRandomImageInsert () :: '+doRandomImageInsert()+' --')
cy.log(' -- doRandomImageInsertSmall () :: '+doRandomImageInsertSmall()+' --')
cy.log(' -- doRandomImageInsertCk () :: '+doRandomImageInsertCk()+' --')
cy.log(' -- doRandomImageInsertContent () :: '+doRandomImageInsertContent()+' --')
cy.log(' -- globalValues --')
cy.log(' -- globalValues.BRAND_LANG :: '+globalValues.BRAND_LANG+' --')
cy.log(' -- globalValues.timer :: '+globalValues.timer+' --')
cy.log(' -- globalValues.LOGIN_USERNAME :: '+globalValues.LOGIN_USERNAME+' --')
cy.log(' -- globalValues --')
cy.log(' -- globalValues.LoremIpsum :: '+globalValues.LoremIpsum+' --')
cy.log(' -- globalValues.RandomString :: '+globalValues.RandomString+' --')
cy.log(' -- globalValues.MissingFontLightSheetWord :: '+globalValues.MissingFontLightSheetWord+' --')
cy.log(' -- globalValues.contentChanceGenerated :: '+globalValues.contentChanceGenerated+' --')
cy.log(' -- globalValues.randomXpathLabelPagination :: '+globalValues.randomXpathLabelPagination+' --')
cy.log(' -- from Class functions --')
// from Class and utilsVariables.js
// cy.log(' -- brandLang :: '+brandLang+' --')
cy.log(' -- verifierValue :: '+verifierValue+' --')
cy.log(' -- verifier :: '+verifier+' --')
cy.log(' -- entrance :: '+entrance+' --')
cy.log(' -- number :: '+number+' --')
cy.log('buttonCreateContentNumber.articleAccesPanelCollapse :: '+buttonCreateContentNumber.articleAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.urgentAccesPanelCollapse :: '+buttonCreateContentNumber.urgentAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.linkAccesPanelCollapse :: '+buttonCreateContentNumber.linkAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.editionAccesPanelCollapse :: '+buttonCreateContentNumber.editionAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.programAccesPanelCollapse :: '+buttonCreateContentNumber.programAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.pageAccesPanelCollapse :: '+buttonCreateContentNumber.pageAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.slideshowAccesPanelCollapse ::
cy.log('buttonCreateContentNumber.infographicAccesPanelCollapse ::
cy.log('buttonCreateContentNumber.vocabulariesAccesPanelCollapse ::
cy.log('buttonCreateContentNumber.tagmetadataAccesPanelCollapse ::
cy.log('buttonCreateContentNumber.videoAccesPanelCollapse :: '+buttonCreateContentNumber.videoAccesPanelCollapse+'')
cy.log('buttonCreateContentNumber.soundAccesPanelCollapse :: '+buttonCreateContentNumber.soundAccesPanelCollapse+'')
// cy.log(''+generateRandomNum(5)+'')
// cy.log(''+uniqueStringCode(40)+'')
// cy.log(''+randomAlphaCode(''+globalValues.strLength+'')+'')
// cy.log(''+randomAlphaCode(''+globalValues.strLength+'')+'')
// cy.log(''+randomAlphaCode(''+globalValues.strLength+'')+'')
/* code omitted for brevity */

Test file structure is always the same calling functions to minimize spreading the E2E testing suite logic and infos.

describe('BACH :: ensure the minimum BACH functioning @content @update @homepage @tabs :: Testing '+globalValues.BRAND_LANG+' (BachDebug) :: DEBUG @e2e @bach @required', function () {
        before(function () {            
            cy.log('--- BachDebug --- ')
        beforeEach(function () {
                    // Login
                    cy.log('--- Login --- ')
      /* code omitted for brevity */

Most of the values are stored in single file /cypress/fixtures/allValues.js. So, adding values is child’s play

// values.js and variables.js
Do not use number as string place it as number
timer: 2000, // '+globalValues.timer+'
- WRONG :: cy.wait(''+globalValues.timer+'')
- GOOD :: cy.wait(globalValues.timerNavigation)
const globalValues = {
username: Cypress.env("users").admin.LOGIN_USERNAME, // '+globalValues.username+'
password: Cypress.env("users").admin.LOGIN_PASSWORD, // '+globalValues.username+'
/* code omitted for brevity */
imagesDirectoryStatus: Cypress.env('IMAGES_DIRECTORY_STATUS'),
BRAND_LANG: Cypress.env('BRAND_LANG'), // '+globalValues.BRAND_LANG+'
LANG: Cypress.env('LANG'), // '+globalValues.LANG+'
subDirectory: Cypress.env('subDirectory'),
link_entrance: Cypress.env('link_entrance'),
/* code omitted for brevity */
/* New variables for CYPRESS */
TOTEM_TEST_MAN_CYPRESS:'٩(^‿^)۶ --- DONE --- \\(^-^)/',
// '+globalValues.TOTEM_TEST_MAN_CYPRESS+'
// use for  cy.task
strLength:40, //'+globalValues.strLength+'
timer: 1000, // '+globalValues.timer+'
/* code omitted for brevity */
/*--- END ---*/
// do export theme
module.exports = {

From my point of view, I have applied to the maximum of my comprehension 2 of the best practices:

  1. The tests should always be able to be run independently from one another and still pass.
  2. I did not try to use Page Object Design Pattern but rather leverage on reusable Cypress Custom. See this post for more infos:

Any critic? Yep, a small one, in Cypress, the console output is no so much readable instead of the one from CPJS where you have colored syntax output witch I found handy! Maybe there is something available in the Cypress online dashboard or any plugin that maybe will do the job but I did not find it.

Passing the –steps argument in CodeceptJS gave you the logic in the test when launch the test in the console, very useful for newbie and to detect when your test failed.

npx codeceptjs run --config=codecept_RFI_FR.conf.js --steps try_windows_31_main_nav_listing_check_column_type_test.js

Without releasing the code, unfortunately there are security reasons to that, here the directory tree that give an idea if the way the Cypress suite logic is structured

|   |   cypress.env.json
|   |   cypress.json
|   |   package-lock.json
|   |   package.json
|   |
|   |   tsconfig.json
|   |   xunit.xml
|   |   
|   +---allure-report
|   |           
|   +---allure-results
|   |       
|   +---config
|   |       F24_AR.conf.env.json
|   |       F24_EN.conf.env.json
|   |       F24_ES.conf.env.json
|   |       F24_FR.conf.env.json
|   |       MCD_AR.conf.env.json
|   |       OBS_AR.conf.env.json
|   |       OBS_EN.conf.env.json
|   |       OBS_FA.conf.env.json
|   |       OBS_FR.conf.env.json
|   |       RFI_BR.conf.env.json
|   |       RFI_CN.conf.env.json
|   |       RFI_EN.conf.env.json
|   |       RFI_ES.conf.env.json
|   |       RFI_FA.conf.env.json
|   |       RFI_FF.conf.env.json
|   |       RFI_FR.conf.env.json
|   |       RFI_HA.conf.env.json
|   |       RFI_KM.conf.env.json
|   |       RFI_MA.conf.env.json
|   |       RFI_PT.conf.env.json
|   |       RFI_RO.conf.env.json
|   |       RFI_RU.conf.env.json
|   |       RFI_SW.conf.env.json
|   |       RFI_VI.conf.env.json
|   |       
|   +---cypress
|   |   +---downloads
|   |   +---fixtures
|   |   |   |   allValues.js
|   |   |   |   
|   |   |   \---pictures
|   |   |           animal_badger.jpg
|   |   |           animal_bear.jpg
|   |   |           animal_bird.jpg
|   |   |           animal_camel.jpg
|   |   |           animal_elephants.jpg
|   |   |           animal_fawn_deer.jpg
|   |   |           animal_fish_blobfish.jpg
|   |   |           animal_hyena.jpg
|   |   |           animal_nature_bird_flying_red.jpg
|   |   |           animal_pangolin.jpg
|   |   |           animal_red_panda.jpg
|   |   |           animal_reptile_chamaeleo.jpg
|   |   |           animal_rhino.jpg
|   |   |           animal_snake.jpg
|   |   |           animal_squirrel.jpg
|   |   |           animal_tapir_malaisie.jpg
|   |   |           animal_tiger.jpg
|   |   |           animal_zebra.jpg
|   |   |           source_meta_image_89b37ba_636575492-2021-10-o-touron-lithium-hd-014.jpg
|   |   |           
|   |   +---integration
|   |   |   \---bach
|   |   |           001_cypress_bach_content_create_article.spec.js
|   |   |           002_cypress_bach_content_create_edition.spec.js
|   |   |           003_cypress_bach_content_create_external_link.spec.js
|   |   |           004_cypress_bach_content_create_urgent.spec.js
|   |   |           005_cypress_bach_content_create_infographic.spec.js
|   |   |           006_cypress_bach_content_upload_delete_image.spec.js
|   |   |           007_cypress_bach_content_create_video.spec.js
|   |   |           008_cypress_bach_content_create_sound.spec.js
|   |   |           009_cypress_bach_content_create_program.spec.js
|   |   |           010_cypress_bach_content_create_slideshow.spec.js
|   |   |           011_cypress_bach_content_create_slideshow.spec.js
|   |   |           012_cypress_bach_content_create_page.spec.js
|   |   |           etc...
|   |   |           
|   |   +---logs
|   |   |       
|   |   +---plugins
|   |   |       index.js
|   |   |       
|   |   \---support
|   |           commands.js
|   |           index.js
|   |           utilityGetStuffForBach.js
|   |           utils.js
|   |           utilsVariables.js
|   |           
|   +---mochawesome-report
|   |   \---results
|   |       |   fail_04082022_162056-002_cypress_bach_content_create_edition-report.html
|   |       |   fail_04082022_162056-002_cypress_bach_content_create_edition-report.json
|   |       |   fail_04082022_162835-008_cypress_bach_content_create_sound-report.html
|   |       |   etc...
|   |               
|   \---node_modules

So, the command will be like something below:

npx cypress run --env BRAND_LANG="F24_EN" --config-file "config/F24_EN.conf.env.json" --config video=false,screenshotOnRunFailure=false

This forced migration is maybe the starting point for a new book who knows….as it was the use of CodeceptJS and Codeception that triggered the writing process for the previous 2 books. You can check out my amazon page:

This migration was also the opportunity to learn and apply 2 things which aim at one thing only : productivity and swiftness. I know I am turning as a Performance-Obsessed “auto-exploiting labourer in his or her own enterprise” to quote the Byung-Chul Han’s concept*.

*Thanks to Alaa for discovering of this Korean philosopher

  1. Applying better coding practices learnt during my recent python exploration, especially using Classes to minimize and reuse code as much as possible (productivity). So far, I did not know how to write a Class in JavaScript so, based on analogy with Python, I learnt how-to write Classes and then use it in Cypress.
  2. Avoiding waste of time (swiftness). Indeed, it is annoying both inside Cypress as inside CodeceptJS to write a JavaScript Function or a Class and wait until the test finished to ensure that your code is correct! So, in a javascript development environment, writing javascript chunks, focused on logic first, is a much handier approach. That is the reason why I learn how to build-up a dev env with Node.

So, below the answers, developed during this migration, to the 2 minors questions above.

Quickly understanding the OOP Class Concept (productivity)

So, what is a Class? Class is the core-concept in Object-oriented programming. You can apply this effective approach to write programs in any language (Python, PHP and also Javascript). In object-oriented programming, you will write classes that are abstraction or meta-description for real-world things and situations, and you will create objects based on these classes.

I will use Python to create this class but the principle is the same for JavaScript. For instance, let’s abstract a Student, a Dog or a Cube… you can find thousands of examples where you have to define with unique traits the object you want to describe. Think meta description and will get the features of your object.

To making an object from a class, you will do what is called an instantiation and then you work with instances of a class.

One of the Class’s benefice is when you and other programmers write code based on the same kind of logic, you’ll be able to understand each other’s work. You can share the logic even from on language to another, that is exactly what I do from Python to Javascript and vice-versa. As you can see below it exists a logic equivalence between classes made in Javascript or in Python. Personally, I have improved the Class concept understanding through Python learning so I am less reluctant to use the class’s concept in Javascript.

Creating and using a Class
You can model almost anything using classes. Let’s start by writing simples classes for a Cube, a Student or a Dog… that represents any of these object, person or animal. You can define the characteristics that represent that element.

For instance, what do we know about most Student? Well, they all have a name, age, university and a program. We also know that most students pass exam and get grade for those exam. Those four pieces of information (name, age, university and program) and those two behaviors (register and pass exam) will go in our Student class because they’re common to most students.

This class will tell Python how to make an object representing a Student. After our class is written, we’ll use it to make individual instances, each of which represents one specific student. So by analogy, you will then have no trouble switching to JavaScript.

Here is our Student Class made in Python.

class Student:
    'A simple student model.'
    def __init__(self, name, age, university, program): = name
        self.age = age = university
        self.program = program
    def register(self):
        'Simulate the student registration in an university.'
        print( + " is registered at "+ )
    def pass_exam(self):
        'Simulate the student exam in an university.'
        print( + " has passed exam for "+ self.program )

Here is our Student Class converted in javascript.

class Student {

      /* A simple student model. */

          constructor(name, age, university, program){
       = name;
                this.age = age;
       = university;
                this.program = program;
            register() {
                console.log(''' is registered at ''')

            pass_exam() {
                console.log(''' has passed exam for '+this.program+'')

      }// EOC

You can find more examples in my GitHub account in the following files: and check for conversion at example_js_class_007.html.

Setup JavaScript Environment to develop javascript for CodeceptJS or Cypress (swiftness)

I know this is maybe overkill as you can just load the pages inside a browser e.g Chrome or Firefox and access to the console 🙂 but anyway that prepare you to work with framework like CodeceptJS or Cypress as these frameworks are Node-based.

REQUIREMENTS – How to setup javaScript environment (Node)

Again, I will go quickly on the install, giving the commands but starting from scratch on how-to install the environment required. If you have already Homebrew and Node installed on your mac, you can jump directly to the section named SIMPLE.

Here is the commands required for the requirements

  1. Using the macOS Terminal
  2. Installing Xcode’s Command Line Tools
  3. xcode-select --install
  4. Installing and Setting Up Homebrew
  5. /usr/bin/ruby -e "$(curl -fsSL"
  6. Installing Node.js
  7. brew doctor
    # install node
    brew install nodejs
    # check the install node
    node -v
    npm -v
    # eventually make some updates
    brew update
    brew upgrade nodejs

You can check these resources for more detailed instructions:

BASIC SETUP – Set up a quick javascript environment (Node)

Step_1: install

# The idea is to load an html file
cd /Users/brunoflaven/Documents/01_work/blog_articles/write_javascript_class/
# check the requirements
node -v
npm -v

Step_2: create and go to the dir

mkdir setup_js_env_1
cd setup_js_env_1/

Step_3: init the project, 2 ways

# Step_3a init the project and create the package.json with no question
npm init -y
# Step_3b init the project and answer to questions
# install generate the package.json
npm init

Prepare some elements to cut and paste to fill-up details for your project

A setup_js_env_1 to develop locally some javascript
# KWS: 
node, javascript, console, chrome

Step_4: create the file index.html

touch index.html

Step_5: cut and paste in index.html

          This is index page...

Step_6: install a server. I will go finally with http-server. Much more easy to use.

# install lite server
npm install -–save-dev lite-server 
# to uninstall lite-server
npm uninstall -–save-dev lite-server 
# install another one
npm install -–save-dev http-server

Step_7: modify package.json file and add a shortcut to start the server

"scripts": {
    "start": "lite-server"
"scripts": {
    "start": "http-server -o"

Step_8: then you can start the server with the command

npm start

ADVANCED SETUP – Set up a more sophisticated javascript environment (Node, Webpack)

# The idea is to load an html file
cd /Users/brunoflaven/Documents/01_work/blog_articles/write_javascript_class/

Step_1: check the install

node -v
npm -v

Step_2 create and go to the dir

mkdir setup_js_env_2
cd setup_js_env_2/
# to launch directly the project in vs code
code .

Step_2a: create folder for source files called “src”

cd setup_js_env_2/
mkdir src
# src/ will contain all your "source files" and it will all be compiled to a single production file later using webpack.

Step_2b: create another folder called “dist”

cd setup_js_env_2/
mkdir dist
# dist/ will contain all your "production files". The files inside this folder are generated by webpack.

Step_4: create the file index.html in the dist folder

cd dist/
touch index.html

Step_5: cut and paste in index.html

          This is index page...

Step_6: go to the dist

cd dist/
touch index.js

Step_7: install webpack and babel

cd /Users/brunoflaven/Documents/01_work/blog_articles/write_javascript_class/setup_js_env_2/

npm install -–save-dev webpack webpack-cli webpack-dev-server babel-loader babel-core babel-preset-env 

Step_7: configure webpack

# create the file "webpack.config.js" on the root of the project and paste the following code.

touch webpack.config.js

Step_8: create babel configuration file.

# create a .babelrc config in your project root and the paste the following
touch .babelrc

Step_9 add the key “private” under “description” and set its value to “true”


Step_10 remove the line

"main": "index.js",
  "name": "setup_js_env_2",
  "version": "1.0.0",
  "description": "A setup_js_env_2 to develop locally some javascript",
  "private": true,
  "main": "index.js",
  "scripts": {
    "build": "webpack --mode=development",
    "watch": "webpack --watch --mode=development",
    "start": "webpack-dev-server --mode=development --open"
  "keywords": [
  "author": "bflaven",
  "license": "ISC",
  "dependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^8.2.4",
    "babel-preset-env": "^1.7.0",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.8.1"
npm run start

# This command will start the web server, open a tab in your default browser.
# If you make any changes in your source files — files in the "src" folder, webpack will automatically rebuild the code and reload your browser.
# If you want to build the source only once, use "npm run build" instead.
npm run build
npm run watch

Step_11 start the web server, you are done….

npm start


I come to ask myself the only valid question: what is the benefit of this migration with such requirements? Personally, the decision to migrate from CodeceptJS to Cypress is absolutely not justified from my practical point of view! It is unnecessary extra work. The only valid reason is that I am the only one to manipulate this E2E suite, playing de facto a role of developer or a quality manager in addition to my role as PO. This situation is creating a dangerous imbalance by creating a bottleneck in the delivery as the responsibility is not really being supported within the team. However, where there is a learning value there is hope! So, to accept this migration is no more work solely but work as team. That’s not politics, that’s laziness.

More infos