Slack and Simple K2 Integration

One of the big messaging apps for team collaboration is Slack and  is perfect platform to demo how to integrate K2 into it. This will be the first article on how we go about doing this.  Slack is free to use and so is great to demo with and it has an ever growing list of third party integration plugins to play with.

So before we start with the demo,what would we expect a K2 slack plugin to behave? I believe it behave in the following way.

  1. Send notifications of tasks that we have to actiontask
  2. Be notified of when task has been completed and what the action was or the workflow has moved to a certain stage or when there is a workflow escalation.
  3. To be able to action a task from inside Slackhomepage_example_hiretron
  4. To be ask Slack what are my outstanding tasks or what is the status of a certain workflow

So lets starts with points 1 and 2 and deal with simple notifications.

 

Building a Simple messaging app for Slack

Lets start with a simple example, where we can K2 notification messages to Slack, whether its a public message , message to a particular group or a message to an individual person.

First of all we need to sign up for Slack and create a team, which you can down from here . Now we have a slack team, we just need to here  to get access to API for Slack.

api_slack

We are starting simple, so click on “Incoming webhooks” and then click on the link ‘ incoming webhook integration

introwebhooks

Building the web hook url

  1. Choose the channel you want to send the messages to, don’t we will be able to override this later on.

introwebhooks12. Click on the green button ‘Add incoming Webhook integration’

3. You can how see your web hook url, copy that.

introwebhooks24. Further down you can also customize the actual message. I have opted for a K2 look.

introwebhooks35. Click on ‘Save’, we have now created are web hook for incoming messaging.

Slack endpoint assembly

Now we have the web hook, we can how write some code, so K2 can use it. We are going to use a endpoint assembly for this. So we are going to create a class that will take the endpoint and allow us to pass in a message, a optional username and optional group.

private static void PostMessage(Payload payload)
 {
 Encoding _encoding = new UTF8Encoding();
 Uri endpoint = new Uri("web hook here");

string payloadJson = JsonConvert.SerializeObject(payload);
 
 using (System.Net.WebClient client = new System.Net.WebClient())
 {
 System.Collections.Specialized.NameValueCollection data = 
new System.Collections.Specialized.NameValueCollection();
 data["payload"] = payloadJson;

var response = client.UploadValues(endpoint, "POST", data);

//The response text is usually "ok"
 string responseText = _encoding.GetString(response);
 }
 }

Simple code for posting a payload of information to the web hook url

public static void PostMessage(string text, string username = null, string channel = null)
 {
 Payload payload = new Payload()
 {
 Channel = channel,
 Username = username,
 Text = text
 };

PostMessage(payload);
 }

The actual public static method, we will be creating a SmartObject from and then using inside a workflow.

We can then build the solution and take the dll  and now tell K2 about it using the ‘Endpoint Assembly broker’. If you don’t know how to do that view my previous post on creating an Endpoint Assembly.

K2Service

Now just build a SmartObject that uses the service instance you just created.

Slack SMO

We can test it in the SmartObject tester

smotester

When it executes we get this response in Slack

smoslack

No that the SmartObject has been created, we can now use this method inside a workflow

Workflow

I am just going to use a simple workflow for this, that has one task and two actions.

test-workflow

We just going to use a SmartObject event to call the Slack notification SmartObject to send a message to the destination user when a task is generated and then a message to the originator when the task is approved or rejected.

SlackSMOEVENT1

SlackSMOEVENT2.PNG

SlackSMOEVENT4

We do something similar for the approve and reject activities, except we put in the originator name and the message is that the task has been approved or rejected depending on the activity.

When we run the workflow the destination user gets this message in Slack

task

With a link to the task, when they action the task the originator will get this message

task1

Next time we will expand on this by making the notifications more advanced and by allowing the user to ask questions about K2.

The source code for this example can be downloaded from here

Testing a workflow inside a Smartform

So I have posted a few articles on testing in K2, we have looked at Unit Testing,  Testing Smartforms and then just recently more examples of testing a workflow.  After writing that article I realised that there was something missing. What about building a workflow testing tool inside of a Smartform. As it all well and good that we can write unit tests for testing a workflow, but that relies on the workflow developer being able to write code and being that the whole idea of K2 is to build low code , no code solutions. Then there should be away of doing it without the developer having to write code.

There are a number of tools out in the market and they do absolutely fantastic job of testing workflows without having to write code. But I wanted to see if it could be done inside a Smartform.

So my challenge is to build a Smartform App that would test a workflow, with out the tester having to write any code.

The app had to do the following

  1. Start any workflow and check that it has started
  2. Check that tasks have been created and the correct number
  3. Action a task
  4. Checking the task has been completed
  5. Check that the workflow has finished
  6. Check the workflow has gone down the direct path.

The app should all be in Smartforms and should be easy to use.

So lets see how I got on.  So i am going to start with the finished tool and then i will take us through it.

Testing Dashboard

The dashboard shows all the current tests that have been performed and shows the overall percentage of pass and failures.

dashboard

From here you can also click on ‘Create Test’ button to create a new test. Selecting an existing test and clicking on ‘Run Test’ will  run an existing test. Finally double clicking on a test will open up the details about the test.

Test Details

So the test details shows the following information

test details

  1. Workflow being tested
  2. If has already been tested
  3. When it was tested
  4. Overall Pass or Fail
  5. What percentage passed and failed
  6. What was tested and whether each test passed or failed.
  7. It also shows any other tests relating to the workflow being tested.

From the test details you also have the ability to retest the test as well.

Creating a new test

From the dashboard click on ‘Create Test’ button.

test builder.PNG

  1. In the new test form, give the test a name
  2. Select a workflow that you are going to test. Once selected you will then be able to see all the activities of that workflow beneath.test-builder1
  3. Select the activities that the workflow will go throughtest-builder2
  4. The last section is where we build up the actual the tests and what results we expect from those tests.test-builder3

Building a simple test script

The workflow we have selected is a very simple one. It has the following activities

  1. Setup
  2. Task
  3. Approve
  4. Reject
  5. End

test-workflow

Task is where there is a client event which has one slot and goes to the originator.

Now that we have got the basic test details and what route we expect the test to go. We can now build are test script.

  1. Select ‘Start Process’ from the test type drop down list
  2. You will notice that the other text boxes in the row, will be pre populated with information and some will be disabled depending on what information the particular test type requires.
  3. Now we choose comparison sign we are going to use it can be either
    1.  =
    2. >
    3. >=
    4. <
    5. <=
  4. We are going to choose >
  5. In the expected result / comparison box lets enter 0, this is because if workflow starts successfully we expect a value greater than 0.
  6. So ‘Start Process’, needs to know the ‘Folio’ to give the workflow, you can either enter in something. Or if you leave it blank it will take the name we have given earlier to the test.
  7. The next parameter we need to give the test type is the process name. You can either copy the full workflow name and paste into this box or leave it and it will automatically get the workflow name.
  8. The next box is called milliseconds, you can either leave it at 0. Or you can enter in a value. This is useful as it may be your workflow is taking a while to perform an event. So you can enter in a delay to the tests to compensate for this.
  9.  Click on ‘Add’ again , we now what to check the status of the workflow, to make sure that once we started it. It is now active.
  10. So select ‘Get Workflow Status’ from the drop down list
  11. Choose ‘=’ from Sign
  12. Enter ‘Active’ in expected result
  13. We don’t need to enter in anything else
  14. Click on ‘Add’
  15. We now want to check to see if has generated some tasks
  16. Select ‘Get Task Count’
  17. Select ‘=’ in Sign
  18. Expected Result should be 1, as we know it’s only one slot and going to the originator.
  19. ProcessInstanceId, leave that how it is, as the testing framework will replace that with the actual process instance id as run time of the test
  20. Process name either enter in the full name or leave it and it will get automatically populated
  21. Next we can action the task
  22. Select ‘=’ for the sign
  23. In the expected result box enter ‘True’
  24. Where it’s says ‘Action’ we need to enter in the action we expect the test to use. So lets enter ‘Approve’
  25. Where it says ‘Serial Number’ we leave that and like the process instance id, it will be populated by the testing framework at run time.
  26. For Destination user, enter in the destination user that the task is supposed to be for.
  27. When this test type runs it will try and action the task based on the information we have given it (Task Action and Destination User) if the test is successful the actual result will be true.
  28. The last test we are going to do is to check that workflow has gone down the correct path and the correct activities were used. The test will compare the path we expect it to be. which was defined in the ‘Workflow Route’ section to the actual path taken.
  29. Select ‘Compare Activities’ from ‘Test Type’
  30. Select ‘=’ from Sign
  31. Enter in ‘True’ in expected result
  32. Leave ProcessInstanceId and Activities how it is
  33. In the Millisecond box enter ‘4000’ this will make the ‘Compare Activities’ test to wait 4000 milliseconds before starting the test. This will give the workflow time to perform it’s other activities before completing. If this test fails then increase the time. It will then pass.
  34. The test script should now look like thistest-builder4
  35. Now click on ‘Save’
  36. You will now see a read only version of the test
  37. With two buttons ‘Edit Test’ and ‘Run Test’

Running a Test

To run a test, either select a test from the test list and click on ‘Run Test’ or from the test details click on the button ‘Run Test’

Once the test has been completed, you will be displayed the results for each individual test and also the overall test.

resuts

Each individual test will show whether it has passed or failed. If it has passed the status will be true and in green and if had failed it will be false and in red.

For each test that we create we also get the overall test result.

A failed test would be when any of the individual tests has failed

fail

It will show a pie chart showing the % of pass and fail with big black x cross above it.

pass

For an overall Pass we get a tick and of course a 100%

These test results will remain until, you click on ‘Run Test’ again and this will overwrite the previous test results with the new ones.

Continue reading “Testing a workflow inside a Smartform”

Workflow Testing

In a previous article i went through how to unit test K2 workflow, now i have some more examples of testing a workflow from start to finish.

These unit tests just use the out of the box K2 API and Visual Studio’s testing framework. It uses an XML file to store test results and data such as task SN and process instance id.

The code can be downloaded from here 

Starting the workflow

To start a workflow we can use the following API

// <summary>
 /// Starts a workflow
 /// </summary>
 /// <param name="Folio"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public int StartProcess(string Folio, string ProcessName)
 {
 var con = ConfigurationManager.AppSettings;
 int ProcessInstanceId = 0;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.CreateProcessInstance(ProcessName);
 
 K2Proc.Folio = Folio;
 K2Conn.ImpersonateUser(ServiceAccount);
 K2Conn.StartProcessInstance(K2Proc);
 ProcessInstanceId = K2Proc.ID;
 }
 catch (Exception EX)
 {
 ProcessInstanceId = 0;
 }
finally
 {
 K2Conn.Close();
 }
 return ProcessInstanceId;
 }

This method returns the process instance id. We can use this in a unit test and check to see if the value returned from the above method is greater than 0

Test: Unit Test for starting the workflow and check to see if it has started (Common)

/// <summary>
 /// Tests the starting of a workflow, should return a number greater than 0
 /// </summary>
 /// <param name="folio"></param>
 /// <param name="ProceesName"></param>
 /// <returns></returns>
 [TestMethod]
 public void StartWorkflow_Success()
 {
int actual = workflow.StartProcess(this.folio, this.ProcessName);
 this.proceessInstance = actual;
 Results.SaveResult(TestType.ProcessInstance,this.proceessInstance.ToString()); 
 Assert.AreNotEqual(0, actual);
 ProcInstId = this.proceessInstance;
 
 }

Check the workflow is running

Now that the workflow has started, we can run a method to check it’s current status

/// <summary>
 /// Gets the current status of the workflow
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public string GetWorkflowStatus(int processinstanceId)
 {
 string Result = string.Empty;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 switch (K2Proc.Status1)
 {
 case SourceCode.Workflow.Client.ProcessInstance.Status.Active:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString();
 break;
 }case SourceCode.Workflow.Client.ProcessInstance.Status.Completed:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Completed.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Deleted:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Deleted.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Error:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Error.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.New:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.New.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Running:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Running.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Stopped:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Stopped.ToString();
 break;
 }
 }  
 }
 catch (Exception ex)
 { Result = ex.Message; }
 finally
 {
 K2Conn.Close();
 }return Result;
 }

This method returns the current status, we can use this to see if the current workflow is running or if has completed

Test: Check workflow has started (Common)

/// <summary>
 /// Test to check that the workflow has started, by checking it's status
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 [TestMethod]
 public void StartWorkflow_Running_Success()
 {
int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 string actual = workflow.GetWorkflowStatus(this.proceessInstance);
 Results.SaveResult(TestType.StartStatus, actual);
 StringAssert.Equals(SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString(), actual);
 }

We could also take this same unit test and change the SourceCode.Workflow.Client.ProcessInstance.Status.Active to ‘Error’ or ‘Completed’

so we can create tests to see if the workflow is completed or it has gone into error state

 

Checking to see if  a task has been created

This method check to see if a task has been created for the particular process and instance.

public Boolean IsTaskFound(int ProcessInstanceId, string ProcessName)
 {
 Boolean Result = false;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
 foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 Result = true;
 }
 }
 }
 catch (Exception ex)
 {
Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Work List – task found (Common)

Returns true if task has been found

/// <summary>
 /// Tests to see if tasks have been generated for this instance
 /// </summary>
 [TestMethod]
 public void WorkList_TasksFound_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 Boolean actual = false;
 actual = workflow.IsTaskFound(this.proceessInstance, this.ProcessName);
 Assert.AreEqual(true, actual);
 }

 

 

Checking the correct number of tasks has been created

This method gets the number of tasks that has been generated. So we can check if the correct number of tasks.

public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Correct number of tasks created (Common)

Returns the number of tasks, which we can then compare against the amount expected

/// <summary>
 /// Checks that the correct number of tasks is generated
 /// </summary>
 [TestMethod]
 public void WorkList_CorrectNumberOfTasksCreated_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(this.TaskCount, actual);
 }

 

Get task details

This methods takes the task, and gets the details about the task

/// <summary>
 /// Gets details about the task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public List<tasklist> GetTask(int ProcessInstanceId,string ProcessName)
 {
List<tasklist> list = new List<tasklist>();
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 var x = worklistItem.ActivityName;
list.Add(new tasklist
 {
 Status = worklistItem.Status.ToString(),
 Destination = worklistItem.Destination,
 EventName = worklistItem.EventName,
 ActInstDestID = worklistItem.ActInstDestID.ToString(),
 ActivityName = worklistItem.ActivityName,
 SerialNumber = (ProcessInstanceId + "_" + worklistItem.ActInstDestID)
 });

}
 }
 }
 catch (Exception ex)
 {
// Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return list;
 }

 

Test:  Check that the correct task has been created (Common)

Returns the name of the activity which compares against the expected activity

/// <summary>
 /// Gets the activity name of the task
 /// </summary>
 [TestMethod]
 public void WorkList_RetrieveTaskList_Success()
 {
 string Actual = string.Empty;
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
var task = workflow.GetTask(this.proceessInstance, this.ProcessName);
 
 this.SerialNumber = task[0].SerialNumber;
 Results.SaveResult(TestType.SerialNumber, this.SerialNumber);
 this.DestinationUser = task[0].Destination;
 Results.SaveResult(TestType.Destination, this.DestinationUser);
 Actual = task[0].ActivityName;
 Results.SaveResult(TestType.TaskActivity, Actual);
 Assert.AreEqual(this.TaskActivity, Actual);
}

 

Action a task

Actions the task and moves the workflow along

/// <summary>
 /// Actions a task
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean ActionTask(string action, string serialnumber, string destinationuser)
 {
 Boolean result = false;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = true;
 }
 catch (Exception ex)
 {
 result = false;
 }
 finally {
 K2Conn.Close();
 }
 return result;
 }

Test : Action’s the task

Returns true if the action of the task has been successful which we compare against the expected.

/// <summary>
 /// Actions a task
 /// </summary>
 [TestMethod]
 public void Task_GetActions_Sucess()
 {
 Boolean Actual = false;
 this.SerialNumber = Results.GetResult(TestType.SerialNumber);
 this.DestinationUser = Results.GetResult(TestType.Destination);
K2WorkflowMap.TaskDetails task = workflow.OpenTask(this.SerialNumber, this.DestinationUser)[0];
 Actual = workflow.ActionTask(this.TaskAction, this.SerialNumber, this.DestinationUser);
 Assert.AreEqual(true, Actual);
}

This test only accounts for one slot, it can be modified to loop through the task list for the particular instance.

 

Task Count

This method, counts the number of tasks for the current instance

 /// <summary>
 /// Gets the number of active tasks for instance of a workflow
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

 

Test: Check Task Complete (Common)

Test checks to see if the number of the tasks for instance is 0

/// <summary>
 /// checks to see if task is complete by checking that task has gone
 /// </summary>
 [TestMethod]
 public void Task_CheckTaskComplete_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(0, actual);
 }

 

 

Once the main lot of testing is complete we can check that that the correct number of activities have been executed and we can check that the correct activities have ran.

Also using the status method we can check if its completed.

 

Activities correct number

This method gets all the activities that ran during the process instance.

/// <summary>
 /// Gets the list of activities from process instance
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<Activities> GetActivities(string processinstanceId)
{
List<Activities> list = new List<Activities>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Activity_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ProcessInstanceID"].Value = processinstanceId;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Activities
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 ActivityName = item.Properties["ActivityName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Activities
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 ActivityName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally {
serverName.Connection.Close();
 }
return list;
 }

 

Task: Compare Number Of Activities (Common)

This test compares the number of activities that actually ran for the instance against the number expected

/// <summary>
 /// Checks the correct number of activities ran
 /// </summary>
 [TestMethod]
 public void Activities_CompareNumberOfActivities_Success()
 {
 
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
int NumberOfActivitiesRan = workflow.GetActivities(this.proceessInstance.ToString()).Count;
 String[] activities = this.Activities.Split(';');
 int count = 0;
 foreach (var activity in activities)
 {
 count++;
}
Assert.AreEqual(count, NumberOfActivitiesRan);
 }

 

Activities than ran

This method loops through the activities that have ran against the an array of activities

public Boolean CompareActivities(int ProcessInstanceId, string Activities)
 {
Boolean result = false;
List<Activities> instAct = new List<Activities>();
 try
 {
 instAct = GetActivities(ProcessInstanceId.ToString());
String[] activities = Activities.Split(';');
 int count = 0;
 int match = 0;
 foreach (var activity in activities)
 {
 var instanceactivity = instAct.Where(p => p.ActivityName == activity);
foreach (var act in instanceactivity)
 {
 match++;
 }
count++;
 }
if (count == match)
 { result = true; }
 }
 catch (Exception ex)
 { }
 finally { }
return result;
}

Test: Correct Activities Ran (Common)

This test returns true if the activities ran is the same to the activities in the string array.

 /// <summary>
 /// Checks the correct activities were executed in correct order
 /// </summary>
 /// 
 [TestMethod]
 public void Activities_CorrectActivitiesRan_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
Boolean actual = workflow.CompareActivities(this.proceessInstance, this.Activities);
 Assert.IsTrue(actual);
 }

 

Code Appendix

Below is the complete code

Interface Iworkflowframework.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace K2WorkflowMap
{
 interface Iworkflowframeworkcs
 {
int StartProcess(string Folio, string ProcessName);
 Boolean IsTaskFound(int ProcessInstanceId, string ProcessName);
 int GetTaskCount(int ProcessInstanceId, string ProcessName);
List<tasklist> GetTask(int ProcessInstanceId, string ProcessName);
 List<TaskDetails> OpenTask(string serialnumber, string destination);
List<ActionList> WhatCanIdo(SourceCode.Workflow.Client.Actions actions);
Boolean ActionTask(string action, string serialnumber, string destinationuser);
Boolean IsTaskComplete(int ProcInst, int Count, string processname);
Boolean IsTaskComplete(string action, string serialnumber, string destinationuser);
string GetWorkflowStatus(int processinstanceId);
List<dataField> GetProcessDataFields(int processinstanceId);
List<Activities> GetActivities(string processinstanceId);
List<Events> GetEvents(string activityid);
List<ActivityDesign> GetWorkflowActivities(int ProcId);
List<Workflow> GetProcesses();
}
}

 

Class Model.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace K2WorkflowMap
{
public enum TestToRun
 {
 Start = 1,
 CheckStatus = 2,
 TaskExists = 3,
 TaskCount = 4,
 CheckTask = 5,
 OpenTaskDetails = 6,
 TaskActions = 7,
 CompleteTask = 8,
 GetActivities = 9
}
 public class tasklist
 {
 public string ActInstDestID { get; set; }
 public string ActivityName { get; set; }
 public string EventName { get; set; }
 public string Destination { get; set; }
 public string Status { get; set; }
 public string SerialNumber { get; set; }
}
public class ActionList
 {
 public string ActionValue { get; set; }
 public string ActionText { get; set; }
 }
public class TaskDetails
 {
public string Status { get; set; }
 public string SerialNumber { get; set; }
 public SourceCode.Workflow.Client.Actions Actions { get; set; }
 public string Data { get; set; }
 public string DataFields { get; set; }
 }
 public class dataField
 {
 public string Name { get; set; }
 public string Value { get; set; }
 public string valueType { get; set; }
 }
public class Activities
 {
 public int ActivityInstanceId { get; set; }
 public int ProcessInstanceId { get; set; }
 public string ActivityName { get; set; }
 public DateTime StartDate { get; set; }
public DateTime FinishDate { get; set; }
 public String Status { get; set; }
 }
public class Events
 {
public int ActivityInstanceId { get; set; }
 public int ProcessInstanceId { get; set; }
 public string EventName { get; set; }
 public string Destination { get; set; }
 public DateTime StartDate { get; set; }
public DateTime FinishDate { get; set; }
 public String Status { get; set; }
 }
public class ActivityDesign
 {
 public int ID { get; set; }
 public string Name { get; set; }
 public string Description { get; set; }
 }
public class Workflow
 {
 public int ID { get; set; }
 public string Name { get; set; }
}
}

Class Results.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.IO;
namespace K2WorkflowUnitTest
{
 public enum TestType
 {
ProcessName,
 Folio,
 ProcessInstance,
 StartStatus,
 TaskCount,
 SerialNumber,
 Destination,
 TaskActivity,
 Actions,
 Activities,
 
 }
 public enum TestSubTypes
 {
Action,
 Activity,
 Event,
 }
public class Results
 {
 private string assemblyFolder;
 private string xmlFileName;
 public Results()
 {
 this.assemblyFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
 this.xmlFileName = Path.Combine(assemblyFolder, "DataStoreResults.xml");
 }
 public void SetUpXML(string ProcessName, string Folio)
 {
 //Gets the settings file
 if (!File.Exists(xmlFileName))
 {
 XmlTextWriter writer = new XmlTextWriter(xmlFileName, System.Text.Encoding.UTF8);
 writer.WriteStartDocument(true);
 writer.Formatting = Formatting.Indented;
 writer.WriteStartElement("Settings");
 writer.WriteStartElement("ProcessName");
 writer.WriteString(ProcessName);
 writer.WriteEndElement();
 writer.WriteStartElement("Folio");
 writer.WriteString(Folio);
 writer.WriteEndElement();
 writer.WriteStartElement("ProcessInstanceId");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("StartStatus");
 writer.WriteString("Stop");
 writer.WriteEndElement();
 writer.WriteStartElement("TaskCount");
 writer.WriteAttributeString("Actual","0");
 writer.WriteEndAttribute();
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("SerialNumber");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Destination");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("TaskActivity");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Actions");
 writer.WriteStartElement("Action");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteStartElement("ActionResult");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteStartElement("Activities");
 
 writer.WriteStartElement("Activity");
 
 writer.WriteString("0");
 writer.WriteStartElement("Event");
 writer.WriteString("0");
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndElement();
 writer.WriteEndDocument();
 writer.Close();
 }
 else
 {
XmlDocument xmlDoc = new XmlDocument();
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[0].InnerXml = ProcessName;
 xmlDoc.ChildNodes[1].ChildNodes[1].InnerXml = Folio;
 xmlDoc.Save(xmlFileName);
 }

}


 /// <summary>
 /// Saves the result into XML Document
 /// </summary>
 /// <param name="testType"></param>
 /// <param name="Result"></param>
 public void SaveResult(TestType testType, string Result)
 {
 int childNodeIndex = 0;
 childNodeIndex = (int)testType;
 XmlDocument xmlDoc = new XmlDocument();
 try
 {
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[childNodeIndex].InnerXml = Result;
 xmlDoc.Save(xmlFileName);
 }
 catch (Exception ex)
 {
}
 finally
 {
}
}
 /// <summary>
 /// Saves the result of sub node in XML Document
 /// </summary>
 /// <param name="testType"></param>
 /// <param name="subTestType"></param>
 /// <param name="Result"></param>
public void SaveResult(TestType testType, TestSubTypes subTestType ,string Result)
 {
 int childNode = 0;
 childNode = (int)testType;
int childNodeIndex = 0;
 childNodeIndex = (int)subTestType;
XmlDocument xmlDoc = new XmlDocument();
 try
 {
 xmlDoc.Load(xmlFileName);
 xmlDoc.ChildNodes[1].ChildNodes[childNode].ChildNodes[childNodeIndex].InnerXml = Result;
 xmlDoc.ChildNodes[1].ChildNodes[childNode].ChildNodes[childNodeIndex].Attributes["Name"].Value = Result;
 xmlDoc.Save(xmlFileName);
 }
 catch (Exception ex)
 {
}
 finally
 {
}
}
 
 /// <summary>
 /// Gets a saved result
 /// </summary>
 /// <param name="testType"></param>
 /// <returns></returns>
 public string GetResult(TestType testType)
 {
int childNodeIndex = 0;
 childNodeIndex = (int)testType;
string results = string.Empty;
 XmlDocument xmlDoc = new XmlDocument();
 xmlDoc.Load(xmlFileName);
 results = xmlDoc.ChildNodes[1].ChildNodes[childNodeIndex].InnerXml;
 return results;
}

}
}

Class Workflowframework.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
namespace K2WorkflowMap
{

public class WorkflowInstanceFramework : Iworkflowframeworkcs
 {
 public string servername = "cft-v-k2int02";
 public string ServiceAccount = @"CHANNEL4\SVCK2MINTMULEUSER";


/// <summary>
 /// Starts a workflow
 /// </summary>
 /// <param name="Folio"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public int StartProcess(string Folio, string ProcessName)
 {
 var con = ConfigurationManager.AppSettings;
 int ProcessInstanceId = 0;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.CreateProcessInstance(ProcessName);
 
 K2Proc.Folio = Folio;
 K2Conn.ImpersonateUser(ServiceAccount);
 K2Conn.StartProcessInstance(K2Proc);
 ProcessInstanceId = K2Proc.ID;
 }
 catch (Exception EX)
 {
 ProcessInstanceId = 0;
 }
finally
 {
 K2Conn.Close();
 }
 return ProcessInstanceId;
 }
/// <summary>
 /// Checks to see if there is an task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <param name="ProcessName"></param>
 /// <returns></returns>
 public Boolean IsTaskFound(int ProcessInstanceId, string ProcessName)
 {
 Boolean Result = false;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
 foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 Result = true;
 }
 }
 }
 catch (Exception ex)
 {
Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }

/// <summary>
 /// Gets the number of active tasks for instance of a workflow
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public int GetTaskCount(int ProcessInstanceId,string ProcessName)
 {
 int Result = 0;
 int count = 0;
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername ,5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 count++;
 }
 }
 Result = count;
 }
catch (Exception ex)
 {
Result = 0;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return Result;
 }
/// <summary>
 /// Gets details about the task
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 /// <returns></returns>
 public List<tasklist> GetTask(int ProcessInstanceId,string ProcessName)
 {
List<tasklist> list = new List<tasklist>();
 SourceCode.Workflow.Management.WorkflowManagementServer wrkmgt = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 SourceCode.Workflow.Management.WorklistItems worklistItems = null;
 wrkmgt.Open();
 try
 {
 worklistItems = wrkmgt.GetWorklistItems("", "", "", "", "", "", "");
foreach (SourceCode.Workflow.Management.WorklistItem worklistItem in worklistItems)
 {
 if (worklistItem.ProcInstID == ProcessInstanceId)
 {
 var x = worklistItem.ActivityName;
list.Add(new tasklist
 {
 Status = worklistItem.Status.ToString(),
 Destination = worklistItem.Destination,
 EventName = worklistItem.EventName,
 ActInstDestID = worklistItem.ActInstDestID.ToString(),
 ActivityName = worklistItem.ActivityName,
 SerialNumber = (ProcessInstanceId + "_" + worklistItem.ActInstDestID)
 });

}
 }
 }
 catch (Exception ex)
 {
// Result = false;
}
 finally
 {
 wrkmgt.Connection.Close();
 }
return list;
 }
 /// <summary>
 /// Task Details
 /// </summary>
 /// <param name="serialnumber"></param>
 /// <param name="destination"></param>
 /// <returns></returns>
 public List<TaskDetails> OpenTask(string serialnumber, string destination)
 {
List<TaskDetails> list = new List<TaskDetails>();
SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destination);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
try
 {
list.Add(new TaskDetails
 {
 Status = K2WListItem.Status.ToString(),
 SerialNumber = K2WListItem.SerialNumber,
 Actions = K2WListItem.Actions,
 Data = K2WListItem.Data,
 DataFields = K2WListItem.ProcessInstance.DataFields.ToString()
 });
 K2WListItem.Release();
 }
 catch (Exception ex)
 {
 list.Add(new TaskDetails
 {
 Status = "No Task"
 });
}
 finally {
 K2Conn.Close();
 }
 return list;
 }
/// <summary>
 /// List of Actions
 /// </summary>
 /// <param name="actions"></param>
 /// <returns></returns>
 public List<ActionList> WhatCanIdo(SourceCode.Workflow.Client.Actions actions)
 {
 List<ActionList> list = new List<ActionList>();
 if (actions != null)
 {
 foreach (SourceCode.Workflow.Client.Action action in actions)
 {
 list.Add(new ActionList
 {
 ActionText = action.Name,
 ActionValue = action.Name
 });
 }
 }
 else {
list.Add(new ActionList
 {
 ActionText = "No Action",
 ActionValue = "No Action"
 });
 }
return list;
 }

 /// <summary>
 /// Actions a task
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean ActionTask(string action, string serialnumber, string destinationuser)
 {
 Boolean result = false;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = true;
 }
 catch (Exception ex)
 {
 result = false;
 }
 finally {
 K2Conn.Close();
 }
 return result;
 }
/// <summary>
 /// Checks to see if the task count has decreased
 /// </summary>
 /// <param name="ProcInst"></param>
 /// <param name="Count"></param>
 /// <returns></returns>
 public Boolean IsTaskComplete(int ProcInst, int Count,string processname)
 {
 Boolean result = false;
 int LastCount = Count;
 int NewCount = GetTaskCount(ProcInst,processname);
 if (LastCount == Count)
 {
 result = false;
 }
 else
 {
 result = true;
 }
return result;
 }
/// <summary>
 /// Checks to see if the actual task that was actioned has been successful
 /// </summary>
 /// <param name="action"></param>
 /// <param name="serialnumber"></param>
 /// <param name="destinationuser"></param>
 /// <returns></returns>
 public Boolean IsTaskComplete(string action, string serialnumber, string destinationuser)
 {
 Boolean result = true;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 K2Conn.ImpersonateUser(destinationuser);
 SourceCode.Workflow.Client.WorklistItem K2WListItem = K2Conn.OpenWorklistItem(serialnumber);
 try
 {
K2WListItem.Actions[action].Execute();
 result = false;
 }
 catch (Exception ex)
 {
 result = true;
 }
 finally
 {
 K2Conn.Close();
 }
 return result;
 }
/// <summary>
 /// Gets the current status of the workflow
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public string GetWorkflowStatus(int processinstanceId)
 {
 string Result = string.Empty;
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 switch (K2Proc.Status1)
 {
 case SourceCode.Workflow.Client.ProcessInstance.Status.Active:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString();
 break;
 }
case SourceCode.Workflow.Client.ProcessInstance.Status.Completed:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Completed.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Deleted:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Deleted.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Error:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Error.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.New:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.New.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Running:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Running.ToString();
 break;
 }
 case SourceCode.Workflow.Client.ProcessInstance.Status.Stopped:
 {
 Result = SourceCode.Workflow.Client.ProcessInstance.Status.Stopped.ToString();
 break;
 }
 }


 }
 catch (Exception ex)
 { Result = ex.Message; }
 finally
 {
 K2Conn.Close();
 }
return Result;
 }
/// <summary>
 /// Gets the process data fields
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<dataField> GetProcessDataFields(int processinstanceId)
 {
 List<dataField> list = new List<dataField>();
 SourceCode.Workflow.Client.Connection K2Conn = new SourceCode.Workflow.Client.Connection();
 K2Conn.Open(servername);
 try
 {
 SourceCode.Workflow.Client.ProcessInstance K2Proc = K2Conn.OpenProcessInstance(processinstanceId);
 foreach (SourceCode.Workflow.Client.DataField datafield in K2Proc.DataFields)
 {
 list.Add(new dataField {
Name = datafield.Name,
 Value = datafield.Value.ToString(),
 valueType = datafield.ValueType.ToString()
});
 }
 }
 catch (Exception ex)
 {
 list.Add(new dataField
 {
Name = "Error",
 Value = ex.Message.ToString()
});
}
 finally {
 K2Conn.Close();
 }
return list;
 }
 public Boolean CompareActivities(int ProcessInstanceId, string Activities)
 {
Boolean result = false;
List<Activities> instAct = new List<Activities>();
 try
 {
 instAct = GetActivities(ProcessInstanceId.ToString());

String[] activities = Activities.Split(';');
 int count = 0;
 int match = 0;
 foreach (var activity in activities)
 {
 var instanceactivity = instAct.Where(p => p.ActivityName == activity);
foreach (var act in instanceactivity)
 {
 match++;
 }
count++;
 }
if (count == match)
 { result = true; }
 }
 catch (Exception ex)
 { }
 finally { }
return result;
}


/// <summary>
 /// Gets the list of activities from process instance
 /// </summary>
 /// <param name="processinstanceId"></param>
 /// <returns></returns>
 public List<Activities> GetActivities(string processinstanceId)
{
List<Activities> list = new List<Activities>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Activity_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ProcessInstanceID"].Value = processinstanceId;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Activities
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 ActivityName = item.Properties["ActivityName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Activities
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 ActivityName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally {
serverName.Connection.Close();
 }
return list;
 }
/// <summary>
 /// Gets a list of Events for an activity
 /// </summary>
 /// <param name="activityid"></param>
 /// <returns></returns>
 public List<Events> GetEvents(string activityid)
 {
 List<Events> list = new List<Events>();
 SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder hostServerConnectionString = new SourceCode.Hosting.Client.BaseAPI.SCConnectionStringBuilder();
 hostServerConnectionString.Host = servername;
 hostServerConnectionString.Port = 5555;
 hostServerConnectionString.IsPrimaryLogin = true;
 hostServerConnectionString.Integrated = true;
SourceCode.SmartObjects.Client.SmartObjectClientServer serverName = new SourceCode.SmartObjects.Client.SmartObjectClientServer();
 serverName.CreateConnection();
 serverName.Connection.Open(hostServerConnectionString.ToString());
try
 {
SourceCode.SmartObjects.Client.SmartObject smartObject = serverName.GetSmartObject("Event_Instance");
 smartObject.MethodToExecute = "List";
 smartObject.Properties["ActivityInstanceID"].Value = activityid;
 SourceCode.SmartObjects.Client.SmartObjectList smoList = serverName.ExecuteList(smartObject);
 foreach (SourceCode.SmartObjects.Client.SmartObject item in smoList.SmartObjectsList)
 {
int ProcInstId = 0;
 int ActInstId = 0;
 int.TryParse(item.Properties["ProcessInstanceID"].Value, out ProcInstId);
 int.TryParse(item.Properties["ActivityInstanceID"].Value, out ActInstId);
DateTime startDate = DateTime.Today;
 DateTime finishDate = DateTime.Today;
 DateTime.TryParse(item.Properties["StartDate"].Value, out startDate);
 DateTime.TryParse(item.Properties["FinishDate"].Value, out finishDate);
list.Add(new Events
 {
 ProcessInstanceId = ProcInstId,
 ActivityInstanceId = ActInstId,
 EventName = item.Properties["EventName"].Value,
 Status = item.Properties["Status"].Value,
 StartDate = startDate,
 FinishDate = finishDate,
 Destination = item.Properties["Destination"].Value
});
 }
}
 catch (Exception ex)
 {
 list.Add(new Events
 {
 ProcessInstanceId = 0,
 ActivityInstanceId = 0,
 EventName = ex.Message,
 Status = "Error",
 StartDate = DateTime.Today,
 FinishDate = DateTime.Today
});
}
 finally
 {
serverName.Connection.Close();
 }
return list;
 }
public void IsErrors(int processinstanceId, string workflowname)
 {



}


/// <summary>
 /// Gets all the activities that are in a workflow
 /// </summary>
 /// <param name="ProcId"></param>
 /// <returns></returns>
 public List<ActivityDesign> GetWorkflowActivities(int ProcId)
 {
 List<ActivityDesign> list = new List<ActivityDesign>();
 SourceCode.Workflow.Management.WorkflowManagementServer workflowServer = new SourceCode.Workflow.Management.WorkflowManagementServer(servername, 5555);
 workflowServer.Open();
 try
 {
 
 foreach (SourceCode.Workflow.Management.Activity activity in workflowServer.GetProcActivities( ProcId))
 {
 list.Add(new ActivityDesign
 {
ID = activity.ID,
 Name = activity.Name,
 Description = activity.Description
});

 }
 }
 catch (Exception ex)
 {
 list.Add(new ActivityDesign
 {
ID = 0,
 Name = "Error",
 Description = ex.Message
 });
 }
 finally
 {
 workflowServer.Connection.Close();
 }
 return list;
}
/// <summary>
 /// Gets a list of all the workflows
 /// </summary>
 /// <returns></returns>
 public List<Workflow> GetProcesses()
 {
 List<Workflow> list = new List<Workflow>();
SourceCode.Workflow.Management.WorkflowManagementServer workflowServer = new SourceCode.Workflow.Management.WorkflowManagementServer(servername,5555);
 workflowServer.Open();
 try
 {
 SourceCode.Workflow.Management.Criteria.ProcessCriteriaFilter filter = new SourceCode.Workflow.Management.Criteria.ProcessCriteriaFilter();
 SourceCode.Workflow.Management.Processes processes = workflowServer.GetProcesses(filter);
 foreach (SourceCode.Workflow.Management.Process process in processes)
 {
 if (process.DefaultVersion == true)
 {
 list.Add(
 new Workflow
 {
 ID = process.ProcID,
 Name = process.FullName
}
);
 }
 }
 }
 catch (Exception ex)
 {
 list.Add(
 new Workflow
 {
 ID = 0,
 Name = ex.Message
}
);
 }
 finally
 {
 workflowServer.Connection.Close();
 }
 return list;
 }
 }
}

 

XML DataStoreResult.xml

Used to hold test data

<?xml version="1.0" encoding="utf-8" ?>
<Settings>
<ProcessName>k2-server-int02</ProcessName>
<Folio></Folio>
<ProcessInstanceId>5555</ProcessInstanceId>
<StartStatus>End</StartStatus>
<TaskCount Actual="1">0</TaskCount>
 <SerialNumber>00_00</SerialNumber>
 <Destination>Bob</Destination>
 <Actions>
 <Action>Approve</Action>
 </Actions>
 <ActionResult>Approve</ActionResult>
 <Activities>
 <Activity>
 <Event name="Status Check">Pass</Event>
 </Activity>
 </Activities>
</Settings>

Unit Test Unittest.cs

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
namespace K2WorkflowUnitTest
{
 [TestClass]
 public class UnitTest1
 {
private string folio;
 private string ProcessName;
 private int proceessInstance;
 private string SerialNumber;
 private string TaskAction;
 private int TaskCount = 0;
 private string Activities;
 private string DestinationUser;
 private string TaskActivity;
 private int milliseconds;
 private SourceCode.Workflow.Client.Actions Actions;
 private K2WorkflowMap.WorkflowInstanceFramework workflow;
 private Results Results;
public int ProcInstId { get; set; }
[TestInitialize]
 public void TestInitialize()
 {
 this.folio = "I am a folio 3";
 this.ProcessName = @"K2Project2\TestProcess";
 this.TaskActivity = "Task";
 this.TaskAction = "Approve";
 this.Activities = "Setup;Task;Approve;End";
 this.TaskCount = 1;
 this.milliseconds = 3000;
 workflow = new K2WorkflowMap.WorkflowInstanceFramework();
 Results = new Results();
 Results.SetUpXML(this.ProcessName, this.folio);
 }
 /// <summary>
 /// Tests the starting of a workflow, should return a number greater than 0
 /// </summary>
 /// <param name="folio"></param>
 /// <param name="ProceesName"></param>
 /// <returns></returns>
 [TestMethod]
 public void StartWorkflow_Success()
 {
int actual = workflow.StartProcess(this.folio, this.ProcessName);
 this.proceessInstance = actual;
 Results.SaveResult(TestType.ProcessInstance, this.proceessInstance.ToString());
 Assert.AreNotEqual(0, actual);
 ProcInstId = this.proceessInstance;
}
/// <summary>
 /// Test to check that the workflow has started, by checking it's status
 /// </summary>
 /// <param name="ProcessInstanceId"></param>
 [TestMethod]
 public void StartWorkflow_Running_Success()
 {
int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 string actual = workflow.GetWorkflowStatus(this.proceessInstance);
 Results.SaveResult(TestType.StartStatus, actual);
 StringAssert.Equals(SourceCode.Workflow.Client.ProcessInstance.Status.Active.ToString(), actual);
 }

/// <summary>
 /// Tests to see if tasks have been generated for this instance
 /// </summary>
 [TestMethod]
 public void WorkList_TasksFound_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 Boolean actual = false;
 actual = workflow.IsTaskFound(this.proceessInstance, this.ProcessName);
 Assert.AreEqual(true, actual);
 }
 /// <summary>
 /// Checks that the correct number of tasks is generated
 /// </summary>
 [TestMethod]
 public void WorkList_CorrectNumberOfTasksCreated_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(this.TaskCount, actual);
 }
 /// <summary>
 /// Gets the activity name of the task
 /// </summary>
 [TestMethod]
 public void WorkList_RetrieveTaskList_Success()
 {
 string Actual = string.Empty;
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
var task = workflow.GetTask(this.proceessInstance, this.ProcessName);
 
 this.SerialNumber = task[0].SerialNumber;
 Results.SaveResult(TestType.SerialNumber, this.SerialNumber);
 this.DestinationUser = task[0].Destination;
 Results.SaveResult(TestType.Destination, this.DestinationUser);
 Actual = task[0].ActivityName;
 Results.SaveResult(TestType.TaskActivity, Actual);
 Assert.AreEqual(this.TaskActivity, Actual);
}
 /// <summary>
 /// Actions a task
 /// </summary>
 [TestMethod]
 public void Task_GetActions_Sucess()
 {
 Boolean Actual = false;
 this.SerialNumber = Results.GetResult(TestType.SerialNumber);
 this.DestinationUser = Results.GetResult(TestType.Destination);
K2WorkflowMap.TaskDetails task = workflow.OpenTask(this.SerialNumber, this.DestinationUser)[0];
 Actual = workflow.ActionTask(this.TaskAction, this.SerialNumber, this.DestinationUser);
 Assert.AreEqual(true, Actual);
}
/// <summary>
 /// checks to see if task is complete by checking that task has gone
 /// </summary>
 [TestMethod]
 public void Task_CheckTaskComplete_Success()
 {
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
 int actual = 0;
 actual = workflow.GetTaskCount(this.proceessInstance, this.ProcessName);
 Results.SaveResult(TestType.TaskCount, actual.ToString());
 Assert.AreEqual(0, actual);
 }
 /// <summary>
 /// Checks the correct number of activities ran
 /// </summary>
 [TestMethod]
 public void Activities_CompareNumberOfActivities_Success()
 {
 
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
int NumberOfActivitiesRan = workflow.GetActivities(this.proceessInstance.ToString()).Count;
 String[] activities = this.Activities.Split(';');
 int count = 0;
 foreach (var activity in activities)
 {
 count++;
}
Assert.AreEqual(count, NumberOfActivitiesRan);
 }
/// <summary>
 /// Checks the correct activities were executed in correct order
 /// </summary>
 /// 
 [TestMethod]
 public void Activities_CorrectActivitiesRan_Success()
 {
 Thread.Sleep(milliseconds);
 int.TryParse(Results.GetResult(TestType.ProcessInstance), out this.proceessInstance);
Boolean actual = workflow.CompareActivities(this.proceessInstance, this.Activities);
 Assert.IsTrue(actual);
 }


}
}

K2 Management Portal: Using the K2 Scheduler

In the latest update of K2, there is now a management portal and one of the key new features of this is the ability to schedule workflows. Which up to now was only available if you had K2 for SharePoint , Appit or if you could decipher the K2 Scheduler API. Your only other option was to write a windows service that would call your scheduled workflow.

 

How to access the K2 Management portal

The K2 management portal can be accessed from here http://[k2servername]/Management

home

The management portal replaces the management console found in the K2 Work space, but doesn’t replace the work space completely.

The K2 Scheduler lives under ‘Workflow Server’ heading in the menu on the left. Expanding ‘Workflow Server’ you have a number of different options. The one we are after is ‘Schedules’ which is the fourth one down.

home1

Schedules

Clicking on Schedules shows a list of currently scheduled workflows and the ability to ‘Add’, ‘Edit’ and ‘Delete’  scheduled workflows from the scheduler

 

Creating a new scheduled workflow

To create a new scheduled workflow follow the following steps

  1. Click on ‘Schedules’
  2. Click on ‘New’
    scheduler
  3.  A form will pop up, for you to fill in
  4. Enter a name of what the scheduled workflow will be called
  5. Enter a description
  6. Enabled is checked by default
  7. Next enter the name of the workflow you want to schedule, either by entering it’s full name or by clicking on the search button to search for it
  8. Once you have found the desired workflow, it’s process data fields will load up and up have the option to populate them with values
  9. The folio can also use the date and time as default or can be edited with a value
    scheduler1
  10. Now if we scroll down the form, we can now tell the scheduler when it should run
  11. Under pattern, we can choose what the scheduling pattern should be

 

Pattern
Description
Once K2 will schedule the workflow to run just once on a certain day and time

once

Daily K2 will schedule the workflow to run every weekday or intervals of days. It also has a start date and time and can be configured to end after a number of occurrences or by a end date and time

daily

Weekly K2 will schedule the workflow to run at least once a week on a certain day. It also has a start date and time and can be configured to end after a number of occurrences or by a end date and time

scheduler3

Monthly K2 will schedule a workflow to run at least once a month, in monthly intervals. It has the ability to schedule a workflow to start on the last or first day of the month or first / last day of the week. It also has a start date and time and can be configured to end after a number of occurrences or by a end date and time

monthly

Yearly K2 will schedule the workflow to run at least once on a particular month. It has the ability to schedule a workflow to start on the last or first day of the month or first / last day of the week. It also has a start date and time and can be configured to end after a number of occurrences or by a end date and time

yearly

Interval With intervals, you can tell K2 to schedule a workflow to start by minutes, hours, days,months and years.It also has a start date and time and can be configured to end after a number of occurrences or by a end date and time

interval

12. Click on ‘Ok’, the scheduled workflow is now scheduled to start at it’s selected interval.

scheduler4

13. From here you can see when it last ran, whether it has been successful or failed and also the ability to enable and disable the scheduled workflow

scheduler5

With K2 releasing the Scheduler in the management portal, this means we can now build some interesting workflows that relies on scheduling of workflows to perform autonomous daily tasks. Such as sending out daily emails to a subscription list.

Testing Smartforms with the Smartform tester in K2 4.7

There are a number of Smartform testing tools currently on the market to allow developers to test their Smartforms. But in 4.7 there is something hidden away that is just dying to be played with. Like the developer’s best friend SmartObject Service Tester, there is now a tool that will allow you to automate testing of your Smartforms.

How to access it

To access this hidden gem of a tool, go to the K2 Designer and click on ‘show’ link at the bottom of the page and in the pop up click on System.

show-all

How you will notice in the context browser for the designer there is now some additional categories.

This slideshow requires JavaScript.

  1. Expand System
  2. Expand Forms
  3. Expand Smartform tester
  4. Click on ‘SmartForms Tester – Landing Page’
  5. Click on the ‘Runtime URL’ in the properties page
  6. The Smartform Tester Portal will now load up and we can start to build a test case

 

SmartForms Tester Portal

This is where we can manage the automating of the forms from creating new tests to running existing ones.

smartform-tester-form

K2 do give us some examples of how to use the tester portal in the ‘Examples’ folder or what they like to call ‘Suites’

So lets create a ‘Suite’

 

Creating a Test Suite

  1. Click on ‘Test Suites’
  2. A list of options will appear at the top
  3. Click on ‘New Suite’
  4. A pop up window will appear enter in the name of  what your suite is going to be called. I am going to call mine ‘Annual Leave’
  5. Click on ‘Accept’
  6. Click on ‘Refresh List’ button
  7. You will now see the suite you just created in the list

This slideshow requires JavaScript.

 

Creating  a new test

  1. Click on the suite you just created
  2. The list options above will change
  3. Click on ‘New Test’
  4. This will open a new window, from where we can build a new test case
  5. Click on smartform-tester-form25 in the Smartform section and choose the View or Smartform you want to test. In this case i am going to create a simple test to test a view
  6. When you select a view/form it will then appear in the in  top section of the testing  form
  7. Below the name of the select artifact
  8. Is the actions this where will build the test script that the portal will execute for us

 

This slideshow requires JavaScript.

Creating the automated test script

  1. In the actions section, you will see there part of a action already selected
  2. Because i have chosen a view to test. The first drop down list has ‘View’ selected. The 2nd drop down list has ‘Control’ selected.
  3. From the 3rd drop down list we can now choose a control on the view
  4. I am going to choose  the picker control ‘pkrUsername’.
  5. A 4th drop down list appears and should have ‘Change Value’ selected
  6. Text box is also available, where a value can be entered. I am going to enter in ‘sallport’smartform-tester-form8
  7. When we execute this action  the tester, will put ‘sallport’ into the picker control for you.
  8. Click on ‘Add’ to add another action smartform-tester-form26
  9. I am going to select the ‘calStartDate’ control and enter in a date and i am going to the same with for ‘calEndDate’
  10. I am going to add another action so it can ‘select’ a leave type. I am going to enter in ‘Paid leave’
  11. Last of all i am going to select the ‘Button’ control and tell it to click the button.
  12. Now that my simple test script of filling out the view and clicking on the button to submit the data. I am ready to get the tester to run it for me.
  13. But before I do that just click on the smartform-tester-form27 in the top left hand corner and select ‘Save’ from the context menu
  14. ‘Save Test’ pop window will appear, enter in a name for test and make sure the correct suite is selected and click on ‘Ok’

This slideshow requires JavaScript.

Running the test

From the test designer, we can either run the whole script or by clicking one of the actions we can test a particular line.

  1. I am going to run the whole script, so click on ‘Run All’ button. The designer will then carry out the actions line by line.
  2. A report will then be displayed telling you if it was successful or not.
  3. The actions line will also all be green and of course the data inserted into the view will be displayed and of course you can check the SmartObject that was used to save the data.
  4. You can also run the test from main testing portal , by selecting the test from the suite and then clicking on ‘Run Test’ option

This slideshow requires JavaScript.

 

So this is a basic how to use the Smartform tester, and there are other options that I have not covered such as delayed actions and modifying variables and assertions such total count of items in a list etc..

Demo forms can be downloaded from here

Conclusion

What is good about this tool it’s all built using Smartforms, so not only are we seeing some new controls that are not made available yet to in the Smartform designer, such as the context menu, the action list with is probably a more advanced list view. I hope these will made available soon.

The 2nd good thing about this is that the test data is available as SmartObjects which mean we can then use these to build rich testing reports that also take into account business data.

It’s great start to something that will hopefully be fleshed out with further updates in the future. So it would be possible to fill out the form, get a task and action it, all in one test case.