5 10 true continue 1 false utterancesFilePath check_account_balance_utterances.csv = Test Plan Directline WebSockets Connect to Direcline and run 5 10 true continue 1 false utterancesFilePath make_payment_utterances.csv = Test Plan Directline WebSockets Connect to Direcline and run copilotTokenEndpoint see here for instructions: https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-connect-bot-to-custom-application#connect-your-copilot-to-a-web-based-app = directlineEndpoint see here for instructions: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-api-reference?view=azure-bot-service-4.0#base-uri = directLineSecret see here for instructions: https://learn.microsoft.com/en-us/microsoft-copilot-studio/configure-web-security#enable-or-disable-web-channel-security = ${__javaScript("${copilotTokenEndpoint}" != "")} false true ${urlDomain} https ${urlPath} true GET true false true // Get the URL from a User Defined Variable def fullUrl = vars.get("copilotTokenEndpoint") // Define the regex patterns to extract parts of the URL def domainPattern = /https:\/\/([^\/]+)/ def pathPattern = /https:\/\/[^\/]+(\/.*)/ // Extract domain and path using the patterns def domainMatcher = (fullUrl =~ domainPattern) def pathMatcher = (fullUrl =~ pathPattern) // Check if matches are found and assign them to variables if (domainMatcher) { vars.put("urlDomain", domainMatcher[0][1]) } else { vars.put("urlDomain", "not_found") } if (pathMatcher) { vars.put("urlPath", pathMatcher[0][1]) } else { vars.put("urlPath", "not_found") } // Log the results for verification log.info("Extracted Domain: ${vars.get("urlDomain")}") log.info("Extracted Path: ${vars.get("urlPath")}") groovy token $.token ${__javaScript("${copilotTokenEndpoint}" == "")} false true Authorization Bearer ${directLineSecret} ${directlineEndpoint} https v3/directline/tokens/generate true POST true false token $.token Authorization Bearer ${token} directline.botframework.com https /v3/directline/conversations true POST true false conversationId; conversationToken; streamUrl $.conversationId; $.token; $.streamUrl null;null;null groovy true def streamUrl = vars.get("streamUrl") log.info(streamUrl) // Remove the 'wss://' prefix def urlWithoutProtocol = streamUrl.replaceFirst("wss://", "") // Split the URL into hostname and path def splitIndex = urlWithoutProtocol.indexOf('/') def hostname = urlWithoutProtocol.substring(0, splitIndex) def path = urlWithoutProtocol.substring(splitIndex) log.info("Hostname: " + hostname) log.info("Path: " + path) // Store the values in variables for further use vars.put("socketHostname", hostname) vars.put("socketPath", path) true ${socketHostname} 443 ${socketPath} 20000 6000 Authorization Bearer ${conversationToken} Content-Type application/json ${__groovy(ctx.getVariables().get("utterance") != "<EOF>")} , ${utterancesFilePath} true false false shareMode.thread false utterance ${__groovy(ctx.getVariables().get("utterance") != "<EOF>")} false true directline.botframework.com https /v3/directline/conversations/${conversationId}/activities true POST true true false { "locale": "en-EN", "type": "message", "from": { "id": "user1" }, "text": "${utterance}" } = groovy true // Get the previous SampleResult object def prevSamplerResult = prev // Get the start time of the previous sampler in milliseconds (epoch time) long startTimeMillis = prevSamplerResult.getStartTime() // Store the start time in a JMeter variable (if needed for future calculations) vars.put("sendUtteranceStartTime", startTimeMillis.toString()) // Log the start time (optional) log.info("Send utterance start time (milliseconds): " + startTimeMillis) groovy true vars.put("websocketSuccess", "true") ${__javaScript(${websocketSuccess} == true)} false 80 20000 Text false 10000 false groovy true // Check if the previous read from the WebSocket was successful if (prev.isSuccessful()) { // Calculate time diffs // Retrieve the start time of the send utterance sampler from JMeter variables long startTimeMillis = vars.get("sendUtteranceStartTime").toLong() // Retrieve the end time of the last successful websocket sampler long endTimeMillis = prev.getEndTime() // Calculate the time difference (elapsed time) long timeDifference = endTimeMillis - startTimeMillis vars.put("tempTimeDifferenceMillis", timeDifference.toString()) vars.put("tempEndTimeMillis", endTimeMillis.toString()) } else { // Get the response code (HTTP status or other protocol-specific code) def responseCode = prev.getResponseCode() // Get the response message (detailed message for the error) def responseMessage = prev.getResponseMessage() def timeDifferenceMillis = vars.get("tempTimeDifferenceMillis") vars.put("timeDifferenceMillis",timeDifferenceMillis) // Log the error details log.error("Error occurred in the previous step.") log.error("Response Code: " + responseCode) log.error("Response Message: " + responseMessage) vars.put("websocketSuccess", "false") // Assuming last successfully read frame was the last Copilot response, based on the error code if (responseCode == "Websocket I/O error") { SampleResult.setSampleLabel("Copilot Response") log.info("sendUtteranceStartTime: " + vars.get("sendUtteranceStartTime") + " tempEndTimeMillis: " + vars.get("sendUtteranceStartTime") + " total tine: " + SampleResult.getTime()) SampleResult.setLatency(timeDifferenceMillis.toLong()) SampleResult.setStartTime(vars.get("sendUtteranceStartTime").toLong()) SampleResult.setEndTime(vars.get("tempEndTimeMillis").toLong()) SampleResult.setResponseMessage("copilot final message response time") } } false true false 1000 6000 false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true Start Conversation true true false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true 500 false false false true false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true 500 false false false