codeceptjs / CodeceptJS

Supercharged End 2 End Testing Framework for NodeJS
http://codecept.io
MIT License
4.11k stars 723 forks source link

Custom helper doesn't work when using babel-node #951

Closed cleverton-heusner closed 6 years ago

cleverton-heusner commented 6 years ago

What are you trying to achieve?

We simply create a Helper for testing purposes, but it isn't even starting. We just want help with the minimum code structure to execute a helper.

PS.: We tried to reproduce the example at https://codecept.io/helpers/, but without success.

What do you get instead?

Output:

Could not load helper WaitForVisibleAndClick from module '[object Object]':
Class constructor Helper cannot be invoked without 'new'
{}

Helper:

'use strict';

class MyHelper extends Helper {

  test() {
    Promise.resolve("Success").then(function(value) {
      return value;
    }, function(value) {});
  }
}

module.exports = MyHelper;

Details

codecept.json

{
  "output": "./output",
  "helpers": {
    "WebDriverIO": {
      "url": "http://localhost:8080",
      "browser": "chrome",
      "restart": true,
      "windowSize": "",
      "desiredCapabilities": {
        "browserName": "chrome",
        "acceptSslCerts": true,
        "chromeOptions": {
          "args": [
            "start-maximized"
          ]
        }
      }
    },
    "WaitForVisibleAndClick": {
      "require": "./helpers/waitforvisibleandclick_helper.js"
    }
  },
  "include": {
    "I": "./steps_file.js",
    "loginPage": "./pages/LoginPage.js",
    "solicitAtivLojaPage": "./pages/SolicitacaoAtivacaoLojaPage.js",
    "statusAtivacaoLojaPage": "./pages/StatusAtivacaoLojaPage.js",
    "menuLateral": "./fragments/menuLateral.js",
    "sellInVendasPage": "./pages/SellInVendasPage.js",
    "sellOutVendasPage": "./pages/SellOutVendasPage.js",
    "selloutPage": "./pages/SelloutPage.js",
    "dashboardLojaPage": "./pages/DashboardLojaPage.js",
    "backofficeLojaPage": "./pages/BackofficeLojaPage.js",
    "gestaoDeAcessosUsuarioPage": "./pages/GestaoDeAcessosUsuariosPage.js"
  },
  "mocha": {},
  "bootstrap": false,
  "teardown": null,
  "hooks": [],
  "tests": "./tests/*.js",
  "timeout": 10000,
  "name": "bdd"
}

package.json

{
  "name": "portal-ivendas-bdd",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "chai": "^4.1.2"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "babel-preset-stage-1": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "babel-preset-stage-3": "^6.24.1",
    "codeceptjs": "^1.1.1",
    "codeceptjs-webdriverio": "^1.1.0",
    "lodash": "^4.17.5",
    "moment": "^2.20.1",
    "pg": "^7.4.1",
    "webdriverio": "^4.10.0"
  },
  "scripts": {
    "test": "mocha",
    "tests": "./node_modules/.bin/babel-node --presets es2015,stage-0,stage-1,stage-2,stage-3 ./node_modules/.bin/codeceptjs run --steps"
  },
  "author": "Automation Dream Team (Cleverton, Luccas e Vinicius)",
  "license": "ISC"
}
reubenmiller commented 6 years ago

There should be no need to run babel-node when running the tests. Node 8.9.4 supports most of the good stuff without requiring any transpiling.

So can you try calling the tests without the babel step?

"scripts": {
    "tests": "./node_modules/.bin/codeceptjs run --steps"
  },

I ran your helper and it looks ok to me.

cleverton-heusner commented 6 years ago

When I try run without babel, I have this:

Could not include object loginPage from module '/home/cleverton/git/ivendas-admin/src/test/codeceptjs/pages/LoginPage.js'
Unexpected token import
reubenmiller commented 6 years ago

Have you tried using require() instead of import?

i.e.

const fs = require('fs');
cleverton-heusner commented 6 years ago

I remember I could use require() instead of import, but I gave up because I couldn't export the modules (classes, functions, constants, etc).

I was thinking on updating my node, but I just have the same ouput about import in a machine with node 9.4.0.

reubenmiller commented 6 years ago

I'm not sure if that will help. I think there is a node cli option which might help.

But to honest I would try to tackle the problem of not being able to use require() instead of the other way around.

cleverton-heusner commented 6 years ago

Would you share your package.json and codecept.json files with me, please? I'd like to compare them with calm. It'd be great.

reubenmiller commented 6 years ago

I don't think that will help because it is pretty much the same as what you posted, except for the babel-node bit.

I would highly suggest removing all import calls and replacing it with require, and then debugging when the require statements don't work.

reubenmiller commented 6 years ago

370 also has a comment saying that transpilers are not supported.

reubenmiller commented 6 years ago

Or maybe you could try using this import-export module.

I just did a quick test, and it loaded without an error, though your mileage might vary.

./configs/codecept.conf.puppeteer.js (I use the javascript style config file instead of json)

require('import-export'); // You need this line here!!!!
const outputFolder = './output';

exports.config = {
  output: outputFolder,
  helpers: {
    Puppeteer: {
      url: 'http://jqueryui.com',
      show: true,
    },
    Mochawesome: {
      uniqueScreenshotNames: true,
    },
    customHelper: {
      require: '../helpers/customHelper.js',
    },
    debugHelper: {
      require: '../helpers/debugHelper.js',
    },
    FileSystem: {},
  },
  include: {
    testPageObj: '../pages/testPageObj.js',
  },
  mocha: {
    reporterOptions: {
      'codeceptjs-cli-reporter': {
        stdout: '-',
        options: {
          verbose: false,
          steps: true,
        },
      },

      'mocha-junit-reporter': {
        stdout: '-',
        options: {
          mochaFile: `${outputFolder}/TestReport.xml`,
        },
      },

      mochawesome: {
        stdout: `${outputFolder}/report-stdout.txt`,
        options: {
          reportDir: outputFolder,
          reportTitle: 'My Test Report Title',
          reportFilename: 'TestReport',
          inlineAssets: true,
        },
      },
    },
  },
  bootstrap: false,
  teardown: null,
  hooks: [],
  tests: '../tests_puppeteer/*_test.js',
  timeout: 10000,
  name: 'doler-js',
};

Example helper ./helpers/debugHelper.js

Note how it uses import and export default (non common js stuff). This is only made possible using the import-export npm module (as stated above).

import assert from 'assert';
let Helper = codecept_helper;

export default class MyHelper extends Helper {

  test() {
    Promise.resolve("Success").then(function(value) {
        console.log('success');
      return value;
    }, function(value) {});
  }
}
cleverton-heusner commented 6 years ago

I didn't know the import-export module. But how I change my codecept file from JSON to JS format? By hand? Besides, is the codecept the only point to include the 'require('import-export')'?

I just to change all the ES6 imports to CommonJS requires and the ES6 exports to CommonJS module.exports and drop the call to babel. Now the output is:

Could not include object loginPage from module '/home/cleverton/git/ivendas-admin/src/test/codeceptjs/pages/LoginPage.js'
Unexpected token *

It complains about the generator function on Test:

Scenario.only('01-Notas Fiscais - Aprovadas', function*(I, loginPage, menuLateral, selloutPage) {
  I.amOnPage('/');
  loginPage.autenticar();

  menuLateral.acessarVendas();
  menuLateral.acessarSellOut();

  selloutPage.gerarRelatorioSellout(LOJA, STATUS_NF_APROVADA);

  let isColunaStatusSelecionada = yield selloutPage.exibirDialogoSelecaoColunas();
  selloutPage.habilitarColunaStatusNotaFiscal(isColunaStatusSelecionada);

  I.see(STATUS_NF_APROVADA);
}); 
reubenmiller commented 6 years ago

The javascript version is pretty much the same thing.

Here is the config that you posted earlier converted to javascript.

codecept.conf.js

require('import-export');

exports.config = {
  output: './output',
  helpers: {
    WebDriverIO: {
      url: 'http://localhost:8080',
      browser: 'chrome',
      restart: true,
      windowSize: '',
      desiredCapabilities: {
        browserName: 'chrome',
        acceptSslCerts: true,
        chromeOptions: {
          args: [
            'start-maximized'
          ]
        }
      }
    },
    WaitForVisibleAndClick: {
      require: './helpers/waitforvisibleandclick_helper.js'
    }
  },
  include: {
    I: './steps_file.js',
    loginPage: './pages/LoginPage.js',
    solicitAtivLojaPage: './pages/SolicitacaoAtivacaoLojaPage.js',
    statusAtivacaoLojaPage: './pages/StatusAtivacaoLojaPage.js',
    menuLateral: './fragments/menuLateral.js',
    sellInVendasPage: './pages/SellInVendasPage.js',
    sellOutVendasPage: './pages/SellOutVendasPage.js',
    selloutPage: './pages/SelloutPage.js',
    dashboardLojaPage: './pages/DashboardLojaPage.js',
    backofficeLojaPage: './pages/BackofficeLojaPage.js',
    gestaoDeAcessosUsuarioPage: './pages/GestaoDeAcessosUsuariosPage.js'
  },
  mocha: {},
  bootstrap: false,
  teardown: null,
  hooks: [],
  tests: './tests/*.js',
  timeout: 10000,
  name: 'bdd'
}

And yes, I'm pretty sure that you only need to use require('import-export'); once. But I haven't really used this module before, so it might not work as it is not 100% feature parity with the native es6 import.

Again I would still recommend migrating all imports to use require for a full native solution in nodejs.

cleverton-heusner commented 6 years ago

I'm following the solution full nodejs this time and now I have the output complaining about generator functions, described in the previous comment.

reubenmiller commented 6 years ago

The error is saying it has a problem with /home/cleverton/git/ivendas-admin/src/test/codeceptjs/pages/LoginPage.js not the test. Or are you writing your tests in your page objects?

reubenmiller commented 6 years ago

And if you're using require instead of import then you can remove the require('import-export'); line.

cleverton-heusner commented 6 years ago

No, I'm writing my tests in tests objects and I'm not using 'import-export' anymore. Fighting an error whose cause is still unkown to tome for some hours:

Could not include object solicitAtivLojaPage from module '/home/cleverton/git/ivendas-admin/src/test/codeceptjs/pages/SolicitacaoAtivacaoLojaPage.js'
Unexpected token =

I'm back tomorrow. Thanks a lot.

cleverton-heusner commented 6 years ago

Hi. 1) I not running the tests with babel anymore; 2) I change the modules sintax to commons js.

I keep having an output which doesn't make any sense to me:

Could not include object solicitAtivLojaPage from module '/home/cleverton/git/ivendas-admin/src/test/codeceptjs/pages/SolicitacaoAtivacaoLojaPage.js'
Unexpected token =

But I don't see anything wrong with this page object:

'use strict';
const {
  Locator
} = require('../locators/SolicitAtivacaoLocator');
const {
  LocatorCommons
} = require('../locators/CommonsLocator');

let I;

module.exports = {
  _init() {
    I = require('../steps_file.js')();
  },

  preencherDadosLoja() {
    I.fillField(Locator.NOME_LOJA, 'Automation Dream Team I');
    I.fillField(Locator.CNPJ, '58654345000149');
    I.fillField(Locator.END_CEP, '75535803');
    I.waitForInvisible(LocatorCommons.LOADER, 30);
    I.fillField(Locator.END_NUM, '657');
    I.fillField(Locator.END_COMP, 'Perto de casa');
    I.fillField(Locator.INI_JORNADA, '08:00');
    I.fillField(Locator.FIM_JORNADA, '18:00');
    I.fillField(Locator.METRAGEM, '1000');
    I.fillField(Locator.NUM_FUNC, '5');
    I.click(Locator.SOFTS_PDV);
    I.click(Locator.SOFT_PDV_SELECIONADO);
    I.click(Locator.RECEBIMENTO_MAGICBOX);
    I.fillField(Locator.NUM_SERIE_MAGICBOX, '239546765');
    I.fillField(Locator.QTD_PDVS, '22');
  },

  preencherDadosTecnico() {
    I.fillField(Locator.TECNICO_NOME, 'Gustavo Adolpho');
    I.fillField(Locator.TECNICO_EMAIL, 'gumg90@gmail.com');
    I.fillField(Locator.TECNICO_TEL, '(64)98146-3009');
    I.click(Locator.TECNICO_WHATS);
  },

  preencherDadosGerente() {
    I.fillField(Locator.GERENTE_NOME, 'Gustavo Adolpho');
    I.fillField(Locator.GERENTE_EMAIL, 'gumg90@gmail.com');
    I.fillField(Locator.GERENTE_TEL, '(64)98146-3009');
    I.click(Locator.GERENTE_WHATS);
  },

  selecionarDataInicioContrato() {
    I.click(Locator.CAMPO_DTA_INI_CONTRATO);
    I.waitForElement(Locator.CAL_DTA_INI_CONTRATO, 50);
    I.click(Locator.CAL_DTA_INI_CONTRATO);
    I.click(Locator.BTN_DTA_INI_CONTRATO);
    I.wait(1.5);
  },

  selecionarTipoContrato() {
    I.waitForElement(Locator.TIPOS_CONTRATO, 30);
    I.click(Locator.TIPOS_CONTRATO);
    I.click(Locator.TIPO_CONTRATO_SELECIONADO);
  },

  selecionarSegmento() {
    I.click(Locator.SEGMENTOS);
    I.click(Locator.SEGMENTO_SELECIONADO);
  },

  selecionarArquivoContrato() {
    const path = require('path');
    let arqContrato = path.join(__dirname, '..', 'resources', 'arq_contrato.bmp');
    I.fillField(Locator.ARQ_CONTRATO, arqContrato);
  },

  preencherDadosTecnologia() {
    I.click(Locator.ABA_TECNOLOCIAS);
    I.click(Locator.USA_CERT_A1);
    I.click(Locator.DOC_FISCAL_NFCE);
    I.click(Locator.DOC_FISCAL_NFE);
    I.click(Locator.SO_LINUX);
    I.click(Locator.EQUIP_BEMATECH);
    I.click(Locator.EQUIP_DARUMA);
    I.click(Locator.IP_FIXO);
  },

  cadastrar() {
    I.waitForElement(Locator.FORM_CADASTRO, 10);
    preencherDadosLoja();
    preencherDadosTecnico();
    preencherDadosGerente();
    selecionarDataInicioContrato();
    selecionarTipoContrato();
    selecionarSegmento();
    selecionarArquivoContrato();
    preencherDadosTecnologia();

    I.click(Locator.BTN_ENVIAR_SOLICIT);
    I.waitForInvisible(LocatorCommons.LOADER, 120);
  }
}

Here go the locators:

class Locator {
  static FORM_CADASTRO = {
    name: 'formCadastro'
  };
  static NOME_LOJA = '#solicit_ativ_tf_nom_loja';
  static CNPJ = '#solicit_ativ_tf_cnpj';
  static END_CEP = '//div[@id="solicit_ativ_tf_cep"]//input';
  static END_NUM = '#solicit_ativ_tf_num';
  static END_COMP = '#solicit_ativ_tf_comp';
  static INI_JORNADA = '#solicit_ativ_tf_inicio_jornada'
  static FIM_JORNADA = '#solicit_ativ_tf_fim_jornada';
  static METRAGEM = '#solicit_ativ_tf_area';
  static NUM_FUNC = '#solicit_ativ_tf_qtd_func';
  static SOFTS_PDV = '#solicit_ativ_select_soft_pdv';
  ...
}

module.exports = Locator;

The test object:


const {
  criar,
  remover
} = require('../massas/GeradorMassaDados');

Feature('Solicitação de Ativação de Loja');

 BeforeSuite((I) => {
   massaDados.remover('solicit_ativ_loja_remocao_massa_dados.sql');
   massaDados.criar('solicit_ativ_loja_criacao_massa_dados.sql');
 });

 AfterSuite((I) => {
   massaDados.remover('solicit_ativ_loja_remocao_massa_dados.sql');
 });

Scenario('Solicitar Ativação', (I, loginPage, menuLateral, solicitAtivLojaPage, statusAtivacaoLojaPage) => {
  I.amOnPage('/');
  loginPage.autenticar();
  menuLateral.acessarAtivacaoLoja();
  menuLateral.acessarSolicitacaoAtivacaoLoja();
  solicitAtivLojaPage.cadastrar();
  menuLateral.acessarStatusAtivacaoLojas();
  statusAtivacaoLojaPage.pesquisarLojaPorCnpj('58654345000149');
  I.see('Automation');
});

And the module GeradorMassaDados:

const {
  ConexaoBD
} = require('../bd/ConexaoBD');

const criar = (script) => {  
  processarConsulta(script);
}

function deArquivo(nomeArquivo) {
  const fs = require('fs');
  const path = require('path');
  const arqSql = path.join(__dirname, '..', 'massas', nomeArquivo);
  return fs.readFileSync(arqSql).toString();
}

function processarConsulta(consulta) {
  const conn = ConexaoBD.obter();
  const sqlCriacaoMassa = deArquivo(consulta);

  conn.connect();
  conn.query(sqlCriacaoMassa, (err, res) => {
    if (err) {
      console.error(err.stack);
    }
    conn.end();
  });
}

const remover = (script) => {  
  processarConsulta(script);
}

module.exports = criar;
module.exports = remover;
reubenmiller commented 6 years ago

There are quite a few errors in the code you provided. You to change how you use the module.exports and how you use require. This is purely a nodejs topics and nothing to do with the framework, though I'll try to give you some pointers.

module.exports

You can only use module.exports = once in a file. In your module GeradorMassaDados this is not the case.

However you can export multiple objects by exporting an object.

module.exports = {
  criar: criar,
  remover: remover
};

And if you use something like this module.exports = Locator; then you need to use the following when requiring it in another file.

const Locator = require('../locators/SolicitAtivacaoLocator');

Or alternatively you can change how you export the Locator to export an object with the Locator proeperty

module.exports = {
   Locator: Locator
};

Then you can use

const {
  Locator
} = require('../locators/SolicitAtivacaoLocator');

// Or
// const Locator = require('../locators/SolicitAtivacaoLocator').Locator;

Does that help?

reubenmiller commented 6 years ago

So you will need to fix all of your exports and require statements in your source files before everything will work well together.

cleverton-heusner commented 6 years ago

I finally finished up the refactoring using helpers and nodejs modules for consume databases and all works magically. Thanks a lot, buddy!!!

reubenmiller commented 6 years ago

You're welcome :)