Categories
discuss

Cypress load environment variables in custom commands

I’m building a Next.js app and write my tests using Cypress. I configure the environment variables using a .env.local file locally. In the CI pipeline, they are defined normally.

I’m trying to write a custom command in Cypress that encrypts a session in cypress/support/command.ts.

import { encryptSession } from 'utils/sessions';

Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-mail',
  } = {}) => {
    const session = { issuer, publicAddress, email };

    return encryptSession(session).then(token => {
      cy.setCookie('my-session-token', token);
      return session;
    });
  },
);

When this command runs, it fails because encryptSession uses a TOKEN_SECRET environment variable, that Cypress doesn’t load.

import Iron from '@hapi/iron';

const TOKEN_SECRET = process.env.TOKEN_SECRET || '';

export function encryptSession(session: Record<string, unknown>) {
  return Iron.seal(session, TOKEN_SECRET, Iron.defaults);
}

How can I get Cypress to load the environment variables from that file, if its there (= only locally because the variables are defined in the CI – it should detect the other variables in the pipeline normally, so the equivalent of detecting a variable that has been set with export MY_VAR=foo)?

Answer

There is Cypress.env, but you want to set the token on process.env which looks like it’s not fully coordinated with the Cypress version.

I know that any process.env with a key with prefix of CYPRESS_ ends up in Cypress.env(), but you want to go in the opposite direction.

I would use a task which gives you access to the file system and process.env,

/cypress/plugins/index.js

module.exports = (on, config) => {
  on('task', {
    checkEnvToken :() =>  {
      const contents = fs.readFileSync('.env.local', 'utf8'); // get the whole file
      const envVars = contents.split('n').filter(v => v);    // split by lines 
                                                              // and remove blanks      
      envVars.forEach(v => {
        const [key, value] = v.trim().split('=');     // split the kv pair
        if (!process.env[key]) {                      // check if already set in CI
          process.env[key] = value;                        
        }
      })
      return null;                                    // required for a task
    },
  })

Call the task ahead of any tests, either in /cypress/support/index.js, or a before(), or in the custom command.

In the custom command

Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-mail',
  } = {}) => {
    cy.task('checkEnvToken').then(() => {  // wait for task to finish 

      const session = { issuer, publicAddress, email };

      return encryptSession(session).then(token => {
        cy.setCookie('my-session-token', token);
          return session;
        });
    })
  });

Digging into the code for @hapi/iron, there is a call to crypto which is a Node library, so you may need to move the whole encryptSession(session) call into a task to make it work.

import { encryptSession } from 'utils/sessions';

module.exports = (on, config) => {
  on('task', {
    encryptSession: (session) =>  {

      const contents = fs.readFileSync('.env.local', 'utf8'); // get the whole file
      const envVars = contents.split('n').filter(v => v);    // split by lines 
                                                              // and remove blanks      
      envVars.forEach(v => {
        const [key, value] = v.trim().split('=');     // split the kv pair
        if (!process.env[key]) {                      // check if already set in CI
          process.env[key] = value;                        
        }
      })

      return encryptSession(session);                 // return the token
    },
  })

Call with

cy.task('encryptSession', { issuer, publicAddress, email })
  .then(token => {
    cy.setCookie('my-session-token', token);
  });

Where to run the above cy.task

I guess you only need to run it once per test session (so that it’s set for a number of spec files) in which case the place to call it is inside a before() in /cypress/support/index.js.

The downside of placing it there is it’s kind of hidden, so personally I’d put it inside a before() at the top of each spec file.

There’s a small time overhead in the fs.readFileSync but it’s minimal compared to waiting for page loads etc.

Categories
discuss

How to add values in duplicated key Map in Java 8

I want to add values in duplicated key Map in Java 8.

As an example:

For example: if strArr is ["B:-1", "A:1", "B:3", "A:5"] then my program should return the string A:6,B:2.

My final output string should return the keys in alphabetical order. Exclude keys that have a value of 0 after being summed up.

Input: new String[] {"X:-1", "Y:1", "X:-4", "B:3", "X:5"}

Output: B:3,Y:1

Input: new String[] {"Z:0", "A:-1"}

Output: A:-1

Tried code:

public static String Output(String[] strArr) {
       //strArr = new String[] {"X:-1", "Y:1", "X:-4", "B:3", "X:5"};
        Map<String, Double> kvs =
                Arrays.asList(strArr)
                    .stream()
                    .map(elem -> elem.split(":"))
                    .collect(Collectors.toMap(e -> e[0], e -> Double.parseDouble(e[1])));
        
        kvs.entrySet().forEach(entry->{
            System.out.println(entry.getKey() + " " + entry.getValue());  
         });
        
        return strArr[0];
      }

Error:

Exception in thread “main” java.lang.IllegalStateException: Duplicate key -1.0

How can I fix this?

Answer

You should declare a merging strategy in the first stream:

.collect(Collectors.toMap(e -> e[0], e -> Double.parseDouble(e[1]), Double::sum));

and then filtered Map by zero value:

  .filter(s-> s.getValue() != 0)

for sorting by key use:

   .sorted(Map.Entry.comparingByKey())

result code:

   String [] strArr = new String[] {"X:-1", "Y:1", "X:-4", "B:3", "X:5"};
    Map<String, Double> kvs =
            Arrays.asList(strArr)
                    .stream()
                    .map(elem -> elem.split(":"))
                    .collect(Collectors.toMap(e -> e[0], e -> Double.parseDouble(e[1]), Double::sum));

    kvs.entrySet().stream()
            .filter(s-> s.getValue() != 0)
            .sorted(Map.Entry.comparingByKey())
            .forEach(entry->{
        System.out.println(entry.getKey() + " " + entry.getValue());w
    });
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..