diff --git a/src/web/client/common/constants.ts b/src/web/client/common/constants.ts index bc2d613b6..6926ee341 100644 --- a/src/web/client/common/constants.ts +++ b/src/web/client/common/constants.ts @@ -3,6 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +import { EXTENSION_NAME } from "../../../common/constants"; +import { getExtensionVersion } from "../../../common/utilities/Utils"; + // Default and constants export const PORTAL_LANGUAGE_DEFAULT = "1033"; export const PORTALS_FOLDER_NAME_DEFAULT = "site"; @@ -135,3 +138,145 @@ export const WEB_EXTENSION_SEND_EMAIL_NOT_AVAILABLE = "Send email is not availab export const WEB_EXTENSION_QUICK_PICK_TITLE = "People on this file" export const WEB_EXTENSION_QUICK_PICK_PLACEHOLDER = "Search for people"; export const WEB_EXTENSION_COLLABORATION_OPTIONS_CONTACT = "Contact"; + +export enum FormConstants { + BasicForm = '[aria-label="Basic Form"]', + EmailField = '[aria-label="Email"]', + SubjectField = '[id="title"]', + MessageField = '[aria-label="Message"]', + SubmitButton = '[id="InsertButton"]', + NameIsRequired = "//li[text()='Name is a required field.']", + NameField = 'input[id="adx_createdbycontact"]', + MinimumRating = 'input[id="minrating"]', + MaximumRating = 'input[id="maxrating"]', + SubmissionSuccessful = "//span[text()='Submission completed successfully.']", + ValidEmail = "//li[text()='Please enter a valid email address.']", + SpecialCharacters = "//li[text()='{0} cannot contain special characters.']", + Lessthan5Characters = "//li[text()='Name must be less than 5 characters.']", + ValidationSummary = '[class*="validation-summary"]', + RatingBeAtleast10 = "//li[text()='Minimum rating must be at least 10.']", + RatingBeAtmost95 = "//li[text()='Maximum Rating must not be greater than 95.']", + RatingBeAtleast5 = "//li[text()='Minimum Rating must be at least 5.']", + RatingAtmost95 = "//li[text()='Maximum Rating must be at most 95.']", +} + +export enum ListConstants { + DataRecord = 'tr[data-name="rf"]', + AmountColumn = '[data-attribute="cr1ae_amount"]', +} + +export enum AdvanceFormConstants { + PhoneField = '#telephone1', + NameField = '#name', + NextButton = '#NextButton', + EmailField = '#emailaddress1', + ValidationSummary = '#ValidationSummaryEntityFormView li', + PreviousButton = '#PreviousButton', + AccountNumber = '#accountnumber', + MessageLabel = '#MessageLabel' +} + +export enum PageConstants { + TestButton = "//button[text()='Test']", + MicrosoftURL = 'https://www.microsoft.com/en-in/', + MicrosoftLogo = 'img[alt="Microsoft Logo"]', + Button = '[type="button"][class*="button"]', + RedColor = 'rgb(255, 0, 0)', + Button1 = '[class="button1"]', + YellowColor = 'rgb(255, 255, 0)', + Section = '.sectionBlockLayout:first-of-type', + GreenColor = 'rgb(0, 128, 0)' +} + +export enum TextConstants{ + MicrosoftURL = 'https://www.microsoft.com/en-in/', + Italic = 'italic' +} + +export enum FormQueries { + Query1 = "Write JavaScript code for name field validation in form.", + Query2 = "Write JavaScript code to hide email field in form.", + Query3 = "Write JavaScript code to disable email field in form.", + Query4 = "Write JavaScript code for form field validation to check email field value is in the valid format.", + Query5 = "Write JavaScript code to add field validation to check for the length of the name field to be less than 5", + Query6 = "Write Javascript code to add field validation to check for the value of the minimum rating field to not to be less than 10.", + Query7 = "Write Javascript code to add field validation to check for the value of the maximum rating field to not to be greater than 95.", + Query8 = "Write Javascript code to add field validation to check for the value of the minimum rating field to not to be less than 5 and maximum rating not to be greater than 95." +} +export enum AdvancedFormQueries { + Query1 = "Write JavaScript code for Phone field validation to check phone field value is in the valid format.", + Query2 = "Write javascript code to rename Next button to Forward", + Query3 = "Write javascript code to make Phone field a required field", + Query4 = "Write javascript code for Account Number field to accept only numbers", + Query5 = "Write javascript code to make email field readonly in the form", +} + +export enum ListQueries { + Query1 = "Write JavaScript code to highlight the row where title column value is rf in table list.", + Query2 = "Write javascript code to Mark rows with amount > 500 with yellow and amount datatype is dollars", + Query3 = "write javascript code to arrange amount column values in descending order", + Query4 = "write javascript code to arrange amount column values in ascending order", +} + +export enum HtmlQueries { + Query1 = "Write code to add 'Account' entity form to my webpage 'html' with some section code.", + Query2 = "Write code to add a button with name Test which should redirect to microsoft.com url.", + Query3 = "Write html code to add an image for microsoft and choose image from online", +} + +export enum CssQueries { + Query1 = "Write css code to change the first section color of home page to yellow", + Query2 = "Write css code to change first existing button background color to red", + Query3 = "Write css code to change button text to italic for all buttons", + Query4 = "Write css code to update first existing button background color to green important on hover", +} + +export enum Paths{ + HtmlWebpageCopy = "C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\web-pages\\html\\content-pages\\Html.en-US.webpage.copy.html", + HomePageCss = 'C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\web-pages\\home\\content-pages\\Home.en-US.webpage.custom_css.css', + ListJsFile = 'C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\lists\\Active-Feedback.list.custom_javascript.js', + FormJsFile = 'C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\basic-forms\\simple-contact-us-form\\simple-contact-us-form.basicform.custom_javascript.js', + AdvFormStep1JsFile = 'C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\advanced-forms\\multistep-form-1\\advanced-form-steps\\step1\\Step1.advancedformstep.custom_javascript.js', + AdvFormStep2JsFile = 'C:\\Users\\v-ankopuri\\Downloads\\CopilotSiteLatest\\latest-site-for-copilot---site-uo67k\\advanced-forms\\multistep-form-1\\advanced-form-steps\\step2\\Step2.advancedformstep.custom_javascript.js' +} + +// Copilot constants +export const AIB_ENDPOINT = 'https://aibuildertextapiservice.us-il108.gateway.prod.island.powerapps.com/v1.0/63efacda-3db4-ee11-a564-000d3a106f1e/appintelligence/chat'; + + +// JSON request to be sent to the API. +export const ApiRequestJson = { + "question": "{0}", + "top": 1, + "context": + { + "scenario": "PowerPagesProDev", + "subScenario": "PowerPagesProDevGeneric", + "version": "V1", + "information": + { + "activeFileContent": "{6}", + "dataverseEntity": "{2}", + "entityField": "{3}", + "fieldType": "{4}", + "targetEntity": "{5}", + "targetColumns": "{1}", // Placeholder value for targetColumns + "clientType": EXTENSION_NAME + '-' + 'Desktop', + "clientVersion": getExtensionVersion() + } + } +}; + +export const ExpectedResponses = { + COPILOT_IT_UNSUPPORTED_EXPECTED_RESPONSE: { + "displayText":"Try a different prompt that’s related to writing code for Power Pages sites. You can get help with HTML, CSS, and JS languages.", + "Code":"violation", + "language":"text", + "useCase":"unsupported" + } +}; + +export const SuggestedPromptsConstants : Record = { + // Name: "Write javascript code to validate name field to not accept special characters", + Subject: "Write javascript code to validate subject field to not accept special characters", +} \ No newline at end of file diff --git a/src/web/client/common/interfaces.ts b/src/web/client/common/interfaces.ts index 850d752e2..a7a78edbe 100644 --- a/src/web/client/common/interfaces.ts +++ b/src/web/client/common/interfaces.ts @@ -4,6 +4,7 @@ */ import * as vscode from 'vscode'; +import fs from 'fs'; export interface IEntityRequestUrl { requestUrl: string; @@ -38,3 +39,18 @@ export interface ISearchQueryResults { matches: ISearchQueryMatch[]; limitHit: boolean; } + +export interface IApiRequestParams { + aibEndPoint: string; + apiToken: string; + data: unknown; +} + +export interface ITestLogParams { + testName: string, + testStartTime: Date, + testEndTime: Date, + actualResponse: string, + status: string, + logStream: fs.WriteStream +} diff --git a/src/web/client/test/integration/copilotPromptsValidation.test.ts b/src/web/client/test/integration/copilotPromptsValidation.test.ts new file mode 100644 index 000000000..3a697cb99 --- /dev/null +++ b/src/web/client/test/integration/copilotPromptsValidation.test.ts @@ -0,0 +1,345 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { CreateAndExecuteAPIRequest, closeHtmlFile, ReturnFormattedAPIResponse, verifyAPIResponse, uploadPortal, LaunchRunTime, reportingForTests, reportAfterTestCompletes, getAccessToken, writeHeadingandTableHeaders, formatString } from '../../utilities/copilotAutomationUtil'; +import { AIB_ENDPOINT, FormConstants, FormQueries, Paths, SuggestedPromptsConstants } from '../../common/constants'; +const aibEndPoint = AIB_ENDPOINT; +let accessToken: string; +import fs from 'fs'; +import { expect } from 'playwright/test'; + +const logStream = reportingForTests(); + +// Overriding the default 10 sec. timeout and setting it to 60 sec. +before(async function () { + if (aibEndPoint === undefined) + throw new Error("Endpoint is not defined. Test will fail intentionally."); + this.timeout(120000); + accessToken = await getAccessToken(); + + // Write heading and table headers to the HTML file + await writeHeadingandTableHeaders(logStream); +}); + +// Run tests for Copilot SUGGESTED prompts +describe('Copilot SUGGESTED prompts integration tests', async function () { + it(FormQueries.Query1, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query1; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Name,adx_createdbycontact,,Email,adx_contactemail,,Subject,title,,Message,comments,,Maximum Rating,maxrating,,Minimum Rating,minrating,,Status Reason,statuscode","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.EmailField).fill('abcd@abc.com'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(FormConstants.NameIsRequired)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(FormQueries.Query2, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query2; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + ["adx_contactemail"], + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await expect(page.locator(FormConstants.EmailField)).toBeHidden(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(FormQueries.Query3, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query3; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Name,adx_createdbycontact,,Email,adx_contactemail,,Subject,title,,Message,comments,,Maximum Rating,maxrating,,Minimum Rating,minrating,,Status Reason,statuscode","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + + await expect(page.locator(FormConstants.EmailField)).toBeDisabled(); + await page.close(); + + // Record end time after test execution + await reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(FormQueries.Query4, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query4 + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Name,adx_createdbycontact,,Email,adx_contactemail,,Subject,title,,Message,comments,,Maximum Rating,maxrating,,Minimum Rating,minrating,,Status Reason,statuscode","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd'); + + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(FormConstants.ValidEmail)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + + Object.keys(SuggestedPromptsConstants).forEach(function (promptKey) { + it(`${SuggestedPromptsConstants[promptKey]}`, async () => { + const testStartTime = new Date(); + const testName = `${SuggestedPromptsConstants[promptKey]}`; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Name,adx_createdbycontact,,Email,adx_contactemail,,Subject,title,,Message,comments,,Maximum Rating,maxrating,,Minimum Rating,minrating,,Status Reason,statuscode","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Test name!@#$%^&*()'); + await page.locator(FormConstants.SubjectField).fill('Test title@%&%&'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd@abc.com'); + await page.locator(FormConstants.SubmitButton).click(); + + await expect(page.locator(formatString(FormConstants.SpecialCharacters,promptKey))).toBeVisible(); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.NameField).fill('Test name'); + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(formatString(FormConstants.SpecialCharacters,promptKey))).not.toBeVisible(); + await expect(page.locator(FormConstants.SubmissionSuccessful)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); +}); + + it(FormQueries.Query5, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query5; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Name,adx_createdbycontact","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us'); + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd'); + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(FormConstants.Lessthan5Characters)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + + it(FormQueries.Query6, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query6; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Minimum Rating,minrating","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd@abc.com'); + await page.locator(FormConstants.MinimumRating).fill('9'); + await page.locator(FormConstants.SubmitButton).click(); + + expect((await page.locator(FormConstants.ValidationSummary).innerText()).toLowerCase()).toContain('minimum rating must be at least 10.'); + await page.locator(FormConstants.MinimumRating).fill('10'); + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(FormConstants.RatingBeAtleast10)).not.toBeVisible(); + await expect(page.locator(FormConstants.SubmissionSuccessful)).toBeVisible(); + + await page.close(); + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(FormQueries.Query7, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query7; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Maximum Rating,maxrating","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd@abc.com'); + + await page.locator(FormConstants.MaximumRating).fill('96'); + await page.locator(FormConstants.SubmitButton).click(); + await expect(page.locator(FormConstants.RatingBeAtmost95)).toBeVisible(); + await page.locator(FormConstants.MaximumRating).fill('95'); + await page.locator(FormConstants.SubmitButton).click(); + + await expect(page.locator(FormConstants.RatingBeAtmost95)).not.toBeVisible(); + await expect(page.locator(FormConstants.SubmissionSuccessful)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(FormQueries.Query8, async () => { + const testStartTime = new Date(); + const testName = FormQueries.Query8; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "Maximum Rating,maxrating,Minimum Rating,minrating","adx_entityform","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.FormJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('contact-us') + + await page.locator(FormConstants.NameField).fill('Text name'); + await page.locator(FormConstants.SubjectField).fill('Test title'); + await page.locator(FormConstants.MessageField).fill('Message'); + await page.locator(FormConstants.EmailField).fill('abcd@abc.com'); + await page.locator(FormConstants.MinimumRating).fill('4'); + await page.locator(FormConstants.MaximumRating).fill('96'); + await page.locator(FormConstants.SubmitButton).click(); + + await expect(page.locator(FormConstants.RatingBeAtleast5)).toBeVisible(); + await expect(page.locator(FormConstants.RatingAtmost95)).toBeVisible(); + await page.locator(FormConstants.MinimumRating).fill('10'); + await page.locator(FormConstants.MaximumRating).fill('90'); + await page.locator(FormConstants.SubmitButton).click(); + + await expect(page.locator(FormConstants.RatingBeAtleast5)).not.toBeVisible(); + await expect(page.locator(FormConstants.RatingAtmost95)).not.toBeVisible(); + await expect(page.locator(FormConstants.SubmissionSuccessful)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); +}); + + +// Close the HTML file with closing tags after all asynchronous code has completed +// Assuming your tests are using Promises, you can return a Promise from your test +// and use the `after` hook to close the logStream after all tests have completed. +after(async function () { + closeHtmlFile(logStream); +}); \ No newline at end of file diff --git a/src/web/client/test/integration/copilotPromptsValidationAdvancedForm.test.ts b/src/web/client/test/integration/copilotPromptsValidationAdvancedForm.test.ts new file mode 100644 index 000000000..f156b8eb6 --- /dev/null +++ b/src/web/client/test/integration/copilotPromptsValidationAdvancedForm.test.ts @@ -0,0 +1,197 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { CreateAndExecuteAPIRequest, closeHtmlFile, ReturnFormattedAPIResponse, verifyAPIResponse, uploadPortal, LaunchRunTime, reportingForTests, reportAfterTestCompletes, getAccessToken, writeHeadingandTableHeaders } from '../../utilities/copilotAutomationUtil'; +import { AIB_ENDPOINT, AdvanceFormConstants, AdvancedFormQueries, Paths } from '../../common/constants'; +const aibEndPoint = AIB_ENDPOINT; +let accessToken: string; +import fs from 'fs'; +import { expect } from 'playwright/test'; + +const logStream = reportingForTests(); +before(async function () { + if (aibEndPoint === undefined) + throw new Error("Endpoint is not defined. Test will fail intentionally."); + this.timeout(120000); + accessToken = await getAccessToken(); + + // Write heading and table headers to the HTML file + await writeHeadingandTableHeaders(logStream); +}); + +// Run tests for Copilot SUGGESTED prompts +describe('Copilot SUGGESTED prompts integration tests', async function () { + it(AdvancedFormQueries.Query1, async () => { + const testStartTime = new Date(); + const testName = AdvancedFormQueries.Query1; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "telephone1","adx_entityform","","","account" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.AdvFormStep1JsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Multistepform'); + await page.locator(AdvanceFormConstants.PhoneField).fill('abc123'); + await page.locator(AdvanceFormConstants.NameField).fill('Test name'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).toHaveText('Phone number is not in a valid format.') + await page.locator(AdvanceFormConstants.PhoneField).fill('+911234567890'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).not.toBeVisible(); + await page.locator(AdvanceFormConstants.PreviousButton).click(); + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).not.toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it( AdvancedFormQueries.Query2, async () => { + const testStartTime = new Date(); + const testName = AdvancedFormQueries.Query2; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "NextButton","adx_entityform","","","account" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.AdvFormStep1JsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Multistepform'); + + expect(await page.getAttribute(AdvanceFormConstants.NextButton,'value')).toBe('Forward'); + expect(await page.getAttribute(AdvanceFormConstants.NextButton,'value')).not.toBe('Next'); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(AdvancedFormQueries.Query3, async () => { + const testStartTime = new Date(); + const testName = AdvancedFormQueries.Query3; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "telephone1","adx_entityform","","","account" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.AdvFormStep1JsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Multistepform'); + + await page.locator(AdvanceFormConstants.NameField).fill('Test name'); + await page.locator(AdvanceFormConstants.NextButton).click(); + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).toHaveText('Phone is a required field.') + + await page.locator(AdvanceFormConstants.NameField).fill('Test name'); + await page.locator(AdvanceFormConstants.PhoneField).fill('911234567890'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).not.toBeVisible(); + await page.locator(AdvanceFormConstants.PreviousButton).click(); + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).not.toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(AdvancedFormQueries.Query4, async () => { + const testStartTime = new Date(); + const testName = AdvancedFormQueries.Query4; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "accountnumber","adx_entityform","","","account" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.AdvFormStep2JsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Multistepform'); + + await page.locator(AdvanceFormConstants.NameField).fill('Test name'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await page.locator(AdvanceFormConstants.AccountNumber).fill('123Abc'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).toBeVisible(); + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).toContainText('Account Number'); + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).toHaveText('Account Number must be a numeric value.') + await page.locator(AdvanceFormConstants.AccountNumber).fill('1234567890'); + await page.locator(AdvanceFormConstants.NextButton).click(); + + await expect(page.locator(AdvanceFormConstants.ValidationSummary)).not.toBeVisible(); + await expect(page.locator(AdvanceFormConstants.MessageLabel)).toHaveText('Submission completed successfully.'); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(240000); + + it(AdvancedFormQueries.Query5, async () => { + const testStartTime = new Date(); + const testName = AdvancedFormQueries.Query5; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "emailaddress1","adx_entityform","","","account" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.AdvFormStep2JsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Multistepform'); + + await page.locator(AdvanceFormConstants.NameField).fill('Test name'); + await page.locator(AdvanceFormConstants.NextButton).click(); + await expect(page.locator(AdvanceFormConstants.EmailField)).toHaveAttribute('readonly'); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(240000); +}); + +// Close the HTML file with closing tags after all asynchronous code has completed +// Assuming your tests are using Promises, you can return a Promise from your test +// and use the `after` hook to close the logStream after all tests have completed. +after(async function () { + closeHtmlFile(logStream); +}); \ No newline at end of file diff --git a/src/web/client/test/integration/copilotPromptsValidationHTMLnCSS.test.ts b/src/web/client/test/integration/copilotPromptsValidationHTMLnCSS.test.ts new file mode 100644 index 000000000..970083195 --- /dev/null +++ b/src/web/client/test/integration/copilotPromptsValidationHTMLnCSS.test.ts @@ -0,0 +1,241 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + + +import { CreateAndExecuteAPIRequest, closeHtmlFile, ReturnFormattedAPIResponse, verifyAPIResponse, uploadPortal, LaunchRunTime, reportingForTests, reportAfterTestCompletes, getAccessToken, writeHeadingandTableHeaders } from '../../utilities/copilotAutomationUtil'; +import { AIB_ENDPOINT, CssQueries, FormConstants, HtmlQueries, PageConstants, Paths, TextConstants } from '../../common/constants'; +const aibEndPoint = AIB_ENDPOINT; +let accessToken: string; +import fs from 'fs'; +import { expect } from 'playwright/test'; + + +const logStream = reportingForTests(); + +// Overriding the default 10 sec. timeout and setting it to 60 sec. +before(async function () { + if (aibEndPoint === undefined) + throw new Error("Endpoint is not defined. Test will fail intentionally."); + this.timeout(120000); + accessToken = await getAccessToken(); + + // Write heading and table headers to the HTML file + await writeHeadingandTableHeaders(logStream); +}); +// Run tests for Copilot SUGGESTED prompts +describe('Copilot Html and Css integration tests', async function () { + it(HtmlQueries.Query1, async () => { + const testName = HtmlQueries.Query1; + const testStartTime = new Date(); + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "","adx_webpage","adx_copy","html","" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HtmlWebpageCopy, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Html', true); + + await page.waitForSelector(FormConstants.BasicForm, {timeout: 60000}); + await expect(page.locator(FormConstants.BasicForm)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName,testStartTime,response,logStream); + }).timeout(120000); + + it(HtmlQueries.Query2,async () => { + const testName = HtmlQueries.Query2; + const testStartTime = new Date(); + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "","adx_webpage","adx_copy","html","" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HtmlWebpageCopy, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Html', true); + + const url = await page.locator(PageConstants.TestButton).getAttribute('onclick'); + expect(url).toContain('microsft.com'); + + await page.click(PageConstants.TestButton); + expect(page.url()).toBe(TextConstants.MicrosoftURL); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + + it(HtmlQueries.Query3, async () => { + const testStartTime = new Date(); + const testName = HtmlQueries.Query3; + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "","adx_webpage","adx_copy","html","" + ]; + + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HtmlWebpageCopy, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Html', true); + + await page.waitForSelector(PageConstants.MicrosoftLogo, {timeout: 60000}); + await expect(page.locator(PageConstants.MicrosoftLogo)).toBeVisible(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + + }).timeout(120000); + + it(CssQueries.Query1, async () => { + const testStartTime = new Date(); + const testName = CssQueries.Query1; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HomePageCss, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Home', true); + + const row = await page.locator(PageConstants.Section).first(); + const backgroundColor = await row.evaluate((element) => { + return window.getComputedStyle(element).backgroundColor; + }); + expect(backgroundColor.toString()).toContain(PageConstants.YellowColor); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(CssQueries.Query2, async () => { + const testStartTime = new Date(); + const testName = CssQueries.Query2; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HomePageCss, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Home',true); + + const button = page.locator(PageConstants.Button1).first(); + const backgroundColor = await button.evaluate((element) => { + return window.getComputedStyle(element).backgroundColor; + }); + expect(backgroundColor.toString()).toContain(PageConstants.RedColor); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(CssQueries.Query3, async () => { + const testStartTime = new Date(); + const testName = CssQueries.Query3; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HomePageCss, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Home',true); + + const buttons = await page.$$(PageConstants.Button); + for (const selector of buttons) { + const fontStyle = await selector?.evaluate((element) => { + return window.getComputedStyle(element).fontStyle; + }); + expect(fontStyle.toString()).toBe(TextConstants.Italic); + } + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); + + it(CssQueries.Query4, async () => { + const testStartTime = new Date(); + const testName = CssQueries.Query4; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.HomePageCss, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('Home',true); + + const button = page.locator(PageConstants.Button1).first(); + await button.hover(); + await page.waitForTimeout(5000); + const backgroundColor = await button.evaluate((element) => { + return window.getComputedStyle(element).backgroundColor; + }); + expect(backgroundColor.toString()).toContain(PageConstants.GreenColor); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName, testStartTime, response, logStream); + }).timeout(120000); +}); + +// Close the HTML file with closing tags after all asynchronous code has completed +// Assuming your tests are using Promises, you can return a Promise from your test +// and use the `after` hook to close the logStream after all tests have completed. +after(async function () { + closeHtmlFile(logStream); +}); diff --git a/src/web/client/test/integration/copilotPromptsValidationList.test.ts b/src/web/client/test/integration/copilotPromptsValidationList.test.ts new file mode 100644 index 000000000..ceb24bf54 --- /dev/null +++ b/src/web/client/test/integration/copilotPromptsValidationList.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { CreateAndExecuteAPIRequest, closeHtmlFile, ReturnFormattedAPIResponse, verifyAPIResponse, uploadPortal, LaunchRunTime, reportingForTests, reportAfterTestCompletes, getAccessToken, writeHeadingandTableHeaders } from '../../utilities/copilotAutomationUtil'; +import { AIB_ENDPOINT, ListConstants, ListQueries, PageConstants, Paths } from '../../common/constants'; +const aibEndPoint = AIB_ENDPOINT; +let accessToken : string; +import fs from 'fs'; +import { expect } from 'playwright/test'; + +const logStream = reportingForTests(); +// Overriding the default 10 sec. timeout and setting it to 60 sec. +before(async function () { + if (aibEndPoint === undefined) + throw new Error("Endpoint is not defined. Test will fail intentionally."); + this.timeout(120000); + accessToken = await getAccessToken(); + + // Write heading and table headers to the HTML file + await writeHeadingandTableHeaders(logStream); +}); + +// Run tests for Copilot SUGGESTED prompts +describe('Copilot List integration tests', async function () { + it(ListQueries.Query1, async () => { + const testStartTime = new Date(); + const testName = ListQueries.Query1; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "title","adx_entitylist","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.ListJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('List'); + + const row = page.locator(ListConstants.DataRecord); + const backgroundColor = await row.evaluate((element) => { + return window.getComputedStyle(element).backgroundColor; + }); + expect(backgroundColor.toString()).toContain(PageConstants.YellowColor); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName,testStartTime,response,logStream); + }).timeout(120000); + + it(ListQueries.Query2, async () => { + const testStartTime = new Date(); + const testName = ListQueries.Query2 ; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "cr1ae_amount","adx_entitylist","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.ListJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('List'); + + const elements=await page.$$(ListConstants.AmountColumn); + for (const element of elements) { + const textContent = await element.innerText(); + const amountValue=parseFloat(textContent.split('$')[1]); + if(amountValue>500){ + const backgroundColor = await element.evaluate((element1) => { + return window.getComputedStyle(element1).backgroundColor; + }); + expect(backgroundColor.toString()).toContain(PageConstants.YellowColor); + } + } + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName,testStartTime,response,logStream); + }).timeout(120000); + + it(ListQueries.Query3, async () => { + const testStartTime = new Date(); + const testName = ListQueries.Query3; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "cr1ae_amount","adx_entitylist","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.ListJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('List'); + + const amounts = await page.evaluate(() => { + const elements = document.querySelectorAll('[data-attribute="cr1ae_amount"]'); + return Array.from(elements).map(el => el.textContent || '').map(text => parseFloat(text.replace('$', '').trim())); + }); + + // Check if amounts are in descending order + const isDescending = amounts.every((value, index, array) => index === 0 || value <= array[index - 1]); + expect(isDescending).toBeTruthy(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName,testStartTime,response,logStream); + }).timeout(120000); + + it(ListQueries.Query4, async () => { + const testStartTime = new Date(); + const testName = ListQueries.Query4; + + // Actual values to replace placeholders from API request JSON. + const actualValues = [ + testName, // question + "cr1ae_amount","adx_entitylist","","","feedback" + ]; + const response = await CreateAndExecuteAPIRequest(testName, actualValues, accessToken, logStream); + + // Assert API response + await verifyAPIResponse(response); + fs.writeFileSync(Paths.ListJsFile, ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)); + await uploadPortal(); + + console.log('Launch browser and navigate to run time'); + const page = await LaunchRunTime('List'); + + const amounts = await page.evaluate(() => { + const elements = document.querySelectorAll('[data-attribute="cr1ae_amount"]'); + return Array.from(elements).map(el => el.textContent || '').map(text => parseFloat(text.replace('$', '').trim())); + }); + + // Check if amounts are in ascending order + const isAscending = amounts.every((value, index, array) => index === 0 || value >= array[index - 1]); + expect(isAscending).toBeTruthy(); + await page.close(); + + // Record end time after test execution + reportAfterTestCompletes(testName,testStartTime,response,logStream); + }).timeout(120000); +}); + +// Close the HTML file with closing tags after all asynchronous code has completed +// Assuming your tests are using Promises, you can return a Promise from your test +// and use the `after` hook to close the logStream after all tests have completed. +after(async function () { + closeHtmlFile(logStream); +}); diff --git a/src/web/client/utilities/copilotAutomationUtil.ts b/src/web/client/utilities/copilotAutomationUtil.ts new file mode 100644 index 000000000..8be113954 --- /dev/null +++ b/src/web/client/utilities/copilotAutomationUtil.ts @@ -0,0 +1,420 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import * as vscode from 'vscode'; +import axios from 'axios'; +import { INTELLIGENCE_SCOPE_DEFAULT, PROVIDER_ID } from "../../../common/services/Constants"; +import { ERROR_CONSTANTS } from "../../../common/ErrorConstants"; +import * as https from 'https' +import fs from 'fs'; +import { promisify } from 'util'; +import { exec } from 'child_process'; +import { chromium } from 'playwright'; +import * as chai from 'chai'; +import path from 'path'; +import { AIB_ENDPOINT, ApiRequestJson, ExpectedResponses } from '../common/constants'; +import { IApiRequestParams, ITestLogParams } from '../common/interfaces'; +const chaiExpect = chai.expect; +const violationOrUnclearResponseCodes : string[] = ["violation", "unclear", "unsupported"]; + +// +// Function to get the access token for the API +// +// Access token +export async function getIntelligenceAPIAccessToken() : Promise<{ accessToken: string }> { + let accessToken = ''; + try { + let session = await vscode.authentication.getSession(PROVIDER_ID, [`${INTELLIGENCE_SCOPE_DEFAULT}`], { silent: true }); + if (!session) { + session = await vscode.authentication.getSession(PROVIDER_ID, [`${INTELLIGENCE_SCOPE_DEFAULT}`], { createIfNone: true }); + } + accessToken = session?.accessToken ?? ''; + if (!accessToken) { + console.log(ERROR_CONSTANTS.NO_ACCESS_TOKEN); + throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); + } + } + catch (error) { + const authError = (error as Error) + console.log("Something went wrong: " + authError) + } + return { accessToken }; +} + +// +// Function to replace the placeholders in the JSON request with actual values +// Replace placeholders with actual values. We can pass more comma separated values matching the placeholders from request json. +// {0} - question +// {1} - activeFileContent +// {2} - dataverseEntity +// {3} - entityField +// {4} - fieldType +// +// Values to replace the placeholders +// JSON request with actual values +export function ReplaceAPIRequestPlaceHoldersAndFormat(values: (string | string[])[]): any { + return JSON.parse(JSON.stringify(ApiRequestJson, (_key, value) => { + if (typeof value === 'string') { + // Replace placeholders in the string + const replacedValue = value.replace(/{(\d+)}/g, (match, index) => { + const replacement = values[index]; + if (Array.isArray(replacement)) { + // Convert array values into a comma-separated string + return replacement.join(', '); + } + return replacement || match; + }); + + // Handle specific case where the string needs to be formatted as an array + if (_key === 'targetColumns') { + // Convert the formatted string to an array + return replacedValue.replace(/,/g, '","').split('","').map(e => e.replace(/^"|"$/g, '')); + } + + return replacedValue; + } + return value; + })); + +} + +// +// Function to create the API request parameters +// +// Actual values to replace the placeholders +// Access token for the API +// API request parameters +export function CreateAPIRequestParams(actualValues: (string | string[])[], accessToken: string) : IApiRequestParams +{ + const data = ReplaceAPIRequestPlaceHoldersAndFormat(actualValues); + console.log('Data returned'+ JSON.stringify(data,null,2)); + return { + aibEndPoint: AIB_ENDPOINT, + apiToken: accessToken, + data: data + }; +} + +// +// Function to get the formatted date and time +// +// Formatted date and time +export function getFormattedDateTime() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hour = String(now.getHours()).padStart(2, '0'); + const minute = String(now.getMinutes()).padStart(2, '0'); + const second = String(now.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day}_${hour}-${minute}-${second}`; +} + +// +// Function to log data to HTML table. +// +// ITestLogParams +export function log(testLogParams: ITestLogParams) { + const statusColor = testLogParams.status === 'PASSED' ? 'green' : 'red'; + testLogParams.logStream.write(` + + ${testLogParams.testName} + ${testLogParams.testStartTime} + ${testLogParams.testEndTime} + ${testLogParams.actualResponse} + ${testLogParams.status} + \n`); + } + +// +// Function to write the heading in the HTML file. +// +// Write stream +// AIB endpoint +export function writeHeading(logStream: fs.WriteStream, aibEndpoint: string) { + logStream.write(`
+ API Details +

AIB Endpoint: ${aibEndpoint}

+
`); +} + +// +// Function to write the table headers in the HTML file. +// +// Write stream +export function writeTableHeaders(logStream: fs.WriteStream) { + logStream.write(` + + + + + + + + + + + \n`); +} + +// +// Function to close the HTML file. +// +// Write stream +export function closeHtmlFile(logStream: fs.WriteStream) { + logStream.write(` + +
Test NameStart TimeEnd TimeCopilot ResponseStatus
+ + `); + logStream.end(); +} + +// +// Function to create and execute the API request. +// +// Test name +// Actual values to replace the placeholders +// Access token for the API +// Write stream +// API response +export async function CreateAndExecuteAPIRequest(testName: string, actualValues: (string | string[])[], accessToken: string, logStream: fs.WriteStream) +{ + let testLogParams: ITestLogParams; + let testStartTime: Date = new Date(); + let testEndTime: Date; + + try { + const params = CreateAPIRequestParams(actualValues, accessToken); + + // Required to get rid of the localhost exception: unable to verify the first certificate. + const agent = new https.Agent(); + + const headers = { + 'Content-Type': 'application/json', + ...(params.aibEndPoint.includes('localhost') + ? { + 'x-ms-client-principal-id': '9ba620dc-4b37-430e-b779-2f9a7e7a52a4', + 'x-ms-client-tenant-id': '9ba620dc-4b37-430e-b779-2f9a7e7a52a3', + } + : {}), + Authorization: `Bearer ${params.apiToken}`, + }; + + testStartTime = new Date(); + + const response = await axios.post(params.aibEndPoint, params.data, { + httpsAgent: agent, + headers: headers, + }); + + return response; + } catch (error) { + testEndTime = new Date(); + const knownError = error as Error; + console.error(`API Error: ${knownError.message}`); + testLogParams = { + testName: testName, + testStartTime: testStartTime, + testEndTime: testEndTime, + actualResponse: knownError.message, + status: `FAILED`, + logStream: logStream + } + log(testLogParams); + throw knownError; + } +} + +export function ReturnFormattedAPIResponse(responseData : []) { + if (!Array.isArray(responseData)) + return responseData; + + type ApiResponse = { displayText: string; code: string }; + type ApiResponseProperties = keyof ApiResponse; + const properties: ApiResponseProperties[] = ['displayText', 'code']; + + let appendedString = ''; + + // Check if responseData is an array + if (Array.isArray(responseData)) { + for (const item of responseData) { + for (const property of properties) { + const propertyValue : string = item[property]; + if (propertyValue !== undefined && propertyValue !== null && propertyValue !== 'explain') { + // Used to replace <, >, and & characters with their corresponding HTML entities. + // This ensures that the text is displayed as-is in the HTML report, and it won't be treated as HTML tags. + const escapedText = propertyValue.replace(/&/g, '&').replace(//g, '>'); //`
${propertyValue}
`; + appendedString += escapedText + "

"; + } + } + } + } + return appendedString; + } + +export const formatString = (str: string, ...args: string[] | number[]) => + str.replace(/{(\d+)}/g, (match, index) => args[index].toString() || ''); + + +export async function verifyAPIResponse(response:any) { + chaiExpect(response).to.have.property('status'); + chaiExpect(response.status).to.equal(200); + chaiExpect(response).to.have.property('data'); + chaiExpect(response.data).to.not.null; + chaiExpect(response.data.operationStatus).to.be.equal('Success'); + const apiResponse = response.data.additionalData[0].properties.response; + chaiExpect(JSON.stringify(apiResponse.useCase)).to.not.equal('unsupported'); + + // Expect that apiResponse.Code is either undefined or does not include any value from the array + chaiExpect(JSON.stringify(apiResponse.Code)).to.satisfy((code: string | unknown[] | undefined) => { + return code === undefined || violationOrUnclearResponseCodes.every((value: string) => !code.includes(value)); + }, 'API response code should be either undefined or not include any of the violation codes'); + chaiExpect(JSON.stringify(apiResponse.displayText)).not.to.equal(ExpectedResponses.COPILOT_IT_UNSUPPORTED_EXPECTED_RESPONSE.displayText); + + console.log('apiResponse code '+ ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response[0].code)) +} + +export async function uploadPortal(){ + const options = { + cwd: 'C:\\Users\\v-ankopuri\\AppData\\Local\\Microsoft\\PowerAppsCli', + }; + const execAsync = promisify(exec); + const { stdout, stderr } = await execAsync('pac paportal upload -p C:/Users/v-ankopuri/Downloads/CopilotSiteLatest/latest-site-for-copilot---site-uo67k -mv 2',options); + chaiExpect(stdout.trim()).to.contain('Power Pages website upload succeeded'); + chaiExpect(stderr).to.be.empty; +} + +export async function LaunchRunTime(pageName:string, timeout = false){ + const browser = await chromium.launch({ headless: false }); // Set headless: false to see the browser UI + const page = await browser.newPage(); + if(timeout){ + await page.waitForTimeout(20000); + } + if(pageName == 'Home'){ + await page.goto('https://site-uo67k.powerappsportals.com/',{timeout:60000}); + } + else{ + await page.goto(formatString('https://site-uo67k.powerappsportals.com/{0}/',pageName),{timeout:60000}); + } + + await page.waitForLoadState(); + await page.waitForLoadState('domcontentloaded'); + return page; +} + +export function reportingForTests(){ + const testReportPath = path.resolve(__dirname, `../test-reports`); // testReportPath => ..\powerplatform-vscode\out\web\client\test\test-reports +// Ensure the log directory exists +if (!fs.existsSync(testReportPath)) { + fs.mkdirSync(testReportPath); +} +const formattedDateTime = getFormattedDateTime(); +// Create a write stream to the HTML file +const logFilePath = path.join(testReportPath, `test-report-${formattedDateTime}.html`); +const logStream = fs.createWriteStream(logFilePath, { flags: 'w' }); +// Open the HTML file with initial structure +logStream.write(` + + + + Copilot Integration Test Report + + + +

Copilot Integration Test Report

+`); +return logStream; +} + +export async function reportAfterTestCompletes(testName:string,testStartTime: Date,response : any,logStream: fs.WriteStream) { + // Record end time after test execution + const testEndTime = new Date(); + const testLogParams: ITestLogParams = { + testName: testName, + testStartTime: testStartTime, + testEndTime: testEndTime, + actualResponse: ReturnFormattedAPIResponse(response.data.additionalData[0].properties.response), + status: 'PASSED', + logStream: logStream + } + log(testLogParams); +} + +export async function getAccessToken() { + const apiToken = await getIntelligenceAPIAccessToken(); + return apiToken.accessToken; +} + +export async function writeHeadingandTableHeaders(logStream:fs.WriteStream) { + writeHeading(logStream, `${AIB_ENDPOINT}`); + writeTableHeaders(logStream); +} + +export { AIB_ENDPOINT, ITestLogParams };