Documentation example

Tools for running user tests against our siteapps

Usage

Extra config

Default codecept config can be overriden like so


    const runTests = require('@dennisdigital/polaris-e2etesting');
    
    runTests({
      extraConfig: {
        helpers: {
          TestCafe: {
            browser: 'safari',
          },
        },
        gherkin: {
          steps: [
            './e2e_tests/step_definitions/steps.js',
          ],
        },
      },
    });
  

extraConfig will be merged with default config and override any already defined options. This allows adding extra site specific step definitions to the gherkin.steps array

Override URL

By default a local server will be spun up running the local sitecode (this assumes the code has been compiled, using either yarn build or yarn watch).

If you wish to use a specific url instead this can be overridden by passing the baseUrl parameter. In this case the local server won't run, instead the tests will just run against the url specified


    const runTests = require('@dennisdigital/polaris-e2etesting');
    
    runTests({
      baseUrl: 'https://example.com',
    });
  

Arguments

Arguments can be passed to the package.json script like so: yarn test:e2e --dev

Development

To develop against polaris-e2etesting from within a siteapp do the following:

Site Level Setup

  1. Install the e2etesting package with yarn

  2. Create e2e_tests folder with following structure:

mkdir e2e_tests e2e_tests/features e2e_tests/tests e2e_tests/output e2e_tests/screenshots && touch e2e_tests/output/.gitignore && echo '*\n!.gitignore' > e2e_tests/output/.gitignore

e2e_tests
│
└───features
│   │   bddtest1.feature
│   │   bddtest2.feature
│   
└───tests
│   │   test1.test.js
│   │   test2.test.js
│   
└───output
└───screenshots
  1. Create runTests.js file

    const runTests = require('@dennisdigital/polaris-e2etesting');
    runTests();
  
  1. Add "test:e2e": "node ./runTests.js" to your package.json scripts section

  2. Update the site's Dockerfile (docker/Dockerfile) to include the following after ARG BUILD_COMMAND

# Install dependencies for running Puppeteer
# https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*
  1. Ensure the siteapp's webpack config does not include the Dotenv plugin for the server bundle (it should still be used for the client bundle)

Code example

Test Runner


    let Container = require('codeceptjs').container;
    let Codecept = require('codeceptjs').codecept;
    const event = require('codeceptjs').event;
    const path = require('path');
    const { spawn } = require('child_process');
    const merge = require('deepmerge');
    const parseArgs = require('minimist');
    const fp = require('find-free-port');
    const codeceptConfig = require('./codecept.conf.js').config;
    
    const appDir = path.dirname(require.main.filename);
    
    /**
    * @param {String} include space separated tags
    * @param {String} exclude space separated tags
    * @returns {String} regex as per https://codecept.io/advanced/#tags
    */
    const tagGrep = (include, exclude) => {
      if (!include && !exclude) return '(?=.*)^(?!.*@exclude)';
      const includeGrep = include ? include.split(' ').map(tag => `(?=.*${tag})`).join('') : '(?=.*)';
      const excludeGrep = exclude ? '^' + exclude.split(' ').map(tag => `(?!.*${tag})`).join('') : '';
      return includeGrep + excludeGrep;
    }
    
    module.exports = (options = {}) => {
      
      const {
        baseUrl,
        extraConfig = {},
        siteConfig
      } = options;
      
      const {
        dev,
        include,
        exclude,
      } = parseArgs(process.argv);
      
      const codeceptInit = (config, opts) => {
        
        // create runner
        let codecept = new Codecept(config, opts);
        
        // initialize codeceptjs in current dir
        codecept.initGlobals(appDir);
        
        // create helpers, support files, mocha
        Container.create(config, opts);
        
        // initialize listeners
        codecept.runHooks();
        
        // run bootstrap function from config
        codecept.runBootstrap((err) => {
          
          // load tests
          codecept.loadTests(config.tests);
          codecept.loadTests(config.gherkin.features);
          
          // run tests
          codecept.run();
        });
      }
      
      const startServerAndRunTests = (port, config, opts) => {
        let testsRunning = false;
        let testsFailed = false;
        
        const server = spawn('yarn', ['server'], {
          env: merge(process.env, {
            PORT: port,
            API_URL: 'https://mock.develop.coreapp.didev.co.uk',
            ALLOW_API_PARAMS: true,
          }),
        });
        
        const killServer = () => {
          process.kill(server.pid);
          if (testsFailed) process.exit(1);
          process.exit(0);
        }
        
        // kill server after any other teardown steps
        const teardown = config.teardown
        ? () => {
          config.teardown();
          killServer();
        } : () => killServer();
        
        server.stdout.on('data', (data) => {
          console.log('\x1b[36m%s\x1b[0m', `e2e test server output:\n${data}`);
          // seems hacky, but want to check server is running before starting tests
          if (data.toString().includes(`http://localhost:${port}`) && !testsRunning) {
            testsRunning = true;
            codeceptInit(merge(config, { teardown }), opts);
          }
        });
        
        event.dispatcher.on(event.test.failed, () => {
          testsFailed = true;
        });
      }
      
      const init = () => {
        fp(3100).then(([port]) => {
          const config = merge(
            codeceptConfig(
              baseUrl || `http://localhost:${port}`,
              dev,
              siteConfig,
              tagGrep(include, exclude),
            ),
            extraConfig
          );
          let opts = { steps: true };

          // if baseurl is provided dont't start the server, just run tests
          if (baseUrl) return codeceptInit(config, opts);

          return startServerAndRunTests(port, config, opts);

        }).catch((err) => {
          console.error(err);
        });
      }
          
      return init();
    }
  

CodeceptJS Config


    const pomPath = `${__dirname}/pageObjectModel`

    exports.config = (
      baseUrl,
      dev = false,
      siteConfig = {},
      grep = '(?=.*)^(?!.*@exclude)',
    ) => ({
      output: './e2e_tests/output',
      tests: './e2e_tests/tests/**/*.test.js',
      grep,
      helpers: {
        Puppeteer: {
          url: baseUrl,
          show: dev,
          windowSize: '1200x800',
          chrome: {
            args: [
              '--no-sandbox',
            ],
          },
        },
        ResembleHelper: {
          require: 'codeceptjs-resemblehelper',
          screenshotFolder: './e2e_tests/output/',
          baseFolder: './e2e_tests/screenshots/base/',
          diffFolder: './e2e_tests/output/',
        },
        SiteConfigHelper: {
          require: __dirname + '/helpers/SiteHelper',
          siteConfig,
        }
      },
      include: {
        I: `${pomPath}/steps_file.js`,
        Base: `${pomPath}/base`,
        Page: `${pomPath}/pages/page/index`,
        ArticlePage: `${pomPath}/pages/article/index`,
        ReviewPage: `${pomPath}/pages/review/index`,
        IndexPage: `${pomPath}/pages/index/index`,
      },
      mocha: {},
      bootstrap: null,
      teardown: null,
      hooks: [],
      gherkin: {
        features: './e2e_tests/features/**/*.feature',
        steps: [
          __dirname + '/step_definitions/steps.js',
        ],
      },
      plugins: {
        screenshotOnFail: {
          enabled: true,
        },
      },
    });