How to update Map GlobalVariable permanently

@Marek_Melocik @kazurayam @grylion54

How to update Map GlobalVariable permanently

The below solutions works for String GlobalVariable . I am looking for a similar solution for MAP, Global Variable aswell. Thanks for looking into this.

On my version of KS (ver 8.2.5), there is a Map data type within the Profiles (last one on the drop-down list of data types). When you select Map, it opens another panel that allows you to set up the key/value pairs. Now, I left it empty because I fill it with the dollar values and debit/credits within the various testcases, (and I only use the one, but I guess I could have made several–one for each test) but since it has the panel for data entry, you should be set to go. Every time you run your test(s), the Global Variable Map will have the same data.
Edit: if you want to write back to the Global Variable, you can do what Marek does by writing to the global variable file that is on disk (or are you wanting assistance on the code). Note that if you write back to the file, it is not guaranteed the file would not be corrupted.
Edit2: So I created a Map in one of my Profiles and it looks similar to any other “initValue”, so Marek’s procedure may work as is. Take a backup of the file beforehand and try it. It if does not work, then delete the corrupted file and replace your backup in its place.

   <GlobalVariableEntity>
      <description></description>
      <initValue>[('bill') : 'debit', ('home') : 'debit', ('money') : 'credit']</initValue>
      <name>whatnot</name>
   </GlobalVariableEntity>

1 Like

I tried using Marek’s method, but the global variable is saved as String.
Below is what I tried.

String newStringValue = '["(KEY)" : "VALUE"]';
util.GlobalVariableUpdater.updatePermanently("TEST", newStringValue)

image

In your example how to update particular value of a key. For example
money to debit, without affecting the other key value pair details.

Thanks.

Marek’s code has a line like this:

elem.getElementsByTagName("initValue").item(0).setTextContent("'" + newValue + "'")

I wonder what will happen if you change this to:

elem.getElementsByTagName("initValue").item(0).setTextContent(newValue)

You want to drop single quotation characters around newValue.

This change might have some unexpected side effects. I don’t know what.

No guarantee but you can try the below. Just add it below the current code from Marek. This overloads his method to work on a Map variable.

I ran it with:

println("Initial Map " + GlobalVariable.whatnot);

//(GlobalVariable.whatnot as Map).put('home', "blah");
(GlobalVariable.whatnot as Map).put('home', "mona lisa");

println("Updated Map " + GlobalVariable.whatnot);

// change selected profile
CustomKeywords.'com.GlobalVariableUpdater.updatePermanently'("test", "whatnot", GlobalVariable.whatnot as Map)

Edit: this is based on a Map I created as in my post above:

   <GlobalVariableEntity>
      <description></description>
      <initValue>[('bill') : 'debit', ('home') : 'debit', ('money') : 'credit']</initValue>
      <name>whatnot</name>
   </GlobalVariableEntity>

Make sure you have a backup copy of your Profiles before “playing” with the below.

Code:
import java.nio.file.Files
import java.nio.file.Paths
import java.lang.StringBuilder

import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult

import org.apache.commons.lang3.StringUtils

import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.NodeList

import com.kms.katalon.core.configuration.RunConfiguration
import com.kms.katalon.core.util.KeywordUtil
import com.kms.katalon.core.annotation.Keyword


	/** WARNING - Permanently updates a Map variable in specified execution profile.<br>
	 * Original value will be replaced with new value and cannot be restored.
	 * @param envName
	 * @param varName
	 * @param newValue
	 */
	@Keyword
	public void updatePermanently(String envName, String varName, Map newValue) {
		File inputFile = new File(RunConfiguration.getProjectDir() + "//Profiles//" + envName + ".glbl")
		if(!Files.exists(Paths.get(inputFile.getAbsolutePath()))) {
			KeywordUtil.markFailed("A file with profile was not found - check path: " + inputFile.getAbsolutePath())
		}

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
		DocumentBuilder builder = factory.newDocumentBuilder()
		Document document = builder.parse(inputFile)

		NodeList elems = document.getDocumentElement().getElementsByTagName("GlobalVariableEntity")
		for (Element elem in elems) {
			if (elem.getElementsByTagName("name").item(0).getTextContent() == varName) {
				StringBuilder myStr = new StringBuilder();
				myStr.append("[")
				// Traversing through Map using for-each loop
				for (Map.Entry<String, String> me : newValue.entrySet()) {
					myStr.append("('")
					myStr.append(me.getKey())
					myStr.append("') : '")
					myStr.append(me.getValue())
					myStr.append("',")
				}
				//println("so far it is " + myStr.toString())
				myStr.deleteCharAt(myStr.length() - 1)
				//println("now it is " + myStr.toString())
				myStr.append("]")
				//println("finally it is " + myStr.toString())
				
				elem.getElementsByTagName("initValue").item(0).setTextContent(myStr.toString())
//				elem.getElementsByTagName("initValue").item(0).setTextContent("'" + newValue + "'")
				document.getDocumentElement().normalize()
				Transformer transformer = TransformerFactory.newInstance().newTransformer()
				DOMSource source = new DOMSource(document)
				StreamResult result = new StreamResult(inputFile)
				transformer.setOutputProperty(OutputKeys.INDENT, "yes")
				transformer.transform(source, result)
				return
			}
		}
		KeywordUtil.markWarning("Global variable with name " + varName + " was not found.")
	}

I would propose an alternative approach. See the following post.

@mtayahi asked

every time i put a new value for the same GlobalVariable, i must run my testcase twice to make the new value taking effect
do you know why ??

I can answer to this question.

To understand my answer, please know what the ./Libs/internal/GlobalVariable.groovy file is. All Katalon Studio projects has this file. This file is auto-generated by Katalon Studio. The content of the file is a Groovy script, like this:

package internal

import com.kms.katalon.core.configuration.RunConfiguration
import com.kms.katalon.core.main.TestCaseMain


/**
 * This class is generated automatically by Katalon Studio and should not be modified or deleted.
 */
public class GlobalVariable {

    /**
     * <p></p>
     */
    public static Object config


    static {
        try {
            def selectedVariables = TestCaseMain.getGlobalVariables("default")
			selectedVariables += TestCaseMain.getGlobalVariables(RunConfiguration.getExecutionProfile())
            selectedVariables += TestCaseMain.getParsedValues(RunConfiguration.getOverridingParameters())

            config = selectedVariables['config']

        } catch (Exception e) {
            TestCaseMain.logGlobalVariableError(e)
        }
    }
}

Then, see the following diagram.

sequence

I will dictate this diagram.

step 0

  • Let’s begin; a tester opens a Katalon project.
  • Katalon Studio will generate the ./Libs/internal/GlobalVariable.groovy on the startup of the project.

step 1

  • A test opens “edit Execution Profile” UI. It will load the ./Profiles/default.glbl XML file and will show how it is written.
  • He will edit the profile and edit the value of GlobalVariable.foo.
  • The UI will serialize the profile into the default.glbl file.
  • He runs a Test Case TC1
  • The GlobalVariable.groovy script will be compiled and run.
  • The GlobalVariable class load the XML files; now TC1 can refer to the values such as GlobalVariable.foo.

step 2

Here I assume we have another Test Case named TC2, which is capable of changing the XML file using the GlobalVariableUpderter class.

  • The tester runs TC2
  • The TC2 updates the XML file
  • When the XML file is updated, no notification will be sent to the “edit Execution Profile” UI.
  • Therefore the “edit Execution Profile” UI will show no change. Old values will remain displayed.

step 3

  • The tester runs TC1 again
  • The updated XML will be loaded and referred to.
  • But the tester still finds the old data in the “edit Execution Profile” UI.

step 4

  • The tester closes the project.
  • When he reopen the project, he will go back tot the step 0.

Discussion

Here I tried the GlobalVariableUpdater class to update GlobalVariable values runtime. It works fine except a point that the “edit Execution Profile” UI will be left un-updated. People would expect the GUI always shows the truth, but it wouldn’t this case.

If a tester operates the “edit Execption Profile”, for example, switching the profile to display, then the UI would be refreshed with the latest XML; then he would see the updated value. He might be confused to see the change at an unexpected timing. This way, I suppose, @mtayashi was confused.

1 Like