From d5e8b17bc94607555daf9d73d435c957e6924162 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 17 Dec 2021 15:17:10 -0800 Subject: [PATCH 01/12] Refactor a lot of code --- .../dist/index.html | 13 +- .../adaptivecards-ui-testapp/index.html | 7 +- .../src/adaptivecards-ui-testapp.css | 17 +- .../src/adaptivecards-ui-testapp.ts | 9 + .../src/file-retriever-utils.ts | 4 +- .../src/rendering-utils.ts | 8 +- .../tests/ui-tests/src/card-element-utils.ts | 165 ++++++++++++++ .../tests/ui-tests/src/selenium-utils.ts | 2 + .../nodejs/tests/ui-tests/src/test-utils.ts | 189 ++++++++++++++++ source/nodejs/tests/ui-tests/src/testUtils.ts | 108 ---------- .../tests/ui-tests/src/ui-tests.test.ts | 201 +++++++++--------- .../nodejs/tests/ui-tests/src/wait-utils.ts | 24 +++ 12 files changed, 524 insertions(+), 223 deletions(-) create mode 100644 source/nodejs/tests/ui-tests/src/card-element-utils.ts create mode 100644 source/nodejs/tests/ui-tests/src/selenium-utils.ts create mode 100644 source/nodejs/tests/ui-tests/src/test-utils.ts delete mode 100644 source/nodejs/tests/ui-tests/src/testUtils.ts create mode 100644 source/nodejs/tests/ui-tests/src/wait-utils.ts diff --git a/source/nodejs/adaptivecards-designer-app/dist/index.html b/source/nodejs/adaptivecards-designer-app/dist/index.html index aa0058069c..6218117768 100644 --- a/source/nodejs/adaptivecards-designer-app/dist/index.html +++ b/source/nodejs/adaptivecards-designer-app/dist/index.html @@ -1 +1,12 @@ -Adaptive Cards Designer
\ No newline at end of file + + + + + Adaptive Cards Designer + + + +
+ + + \ No newline at end of file diff --git a/source/nodejs/adaptivecards-ui-testapp/index.html b/source/nodejs/adaptivecards-ui-testapp/index.html index f9e3aef7da..6fd5a4ea7d 100644 --- a/source/nodejs/adaptivecards-ui-testapp/index.html +++ b/source/nodejs/adaptivecards-ui-testapp/index.html @@ -20,7 +20,12 @@

Adaptive Cards UI Test App

-
+
+
+ +
+
+
diff --git a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.css b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.css index 1bdfad060f..b7f2635e1d 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.css +++ b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.css @@ -16,12 +16,12 @@ body { } .main { - width: 80vw; + width: 60vw; height: 100vh; } .testCasesDiv { - width: 20vw; + width: 40vw; height: 100vh; } @@ -31,13 +31,24 @@ body { height: 80vh; } -#retrievedInputsDiv +.inputsDiv { + display: flex; width: 100vw; height: 20vh; background-color: rgb(240, 238, 238); } +#hideInputsDiv +{ + width: 10vw; +} + +#retrievedInputsDiv +{ + width: 90vw; +} + #renderedCardSpace { width: 600px; diff --git a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts index e7a408a316..127cd9b301 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts @@ -15,6 +15,12 @@ function renderTestCasesList() { }); } +function hideInputsDiv() +{ + let testCaseListElement = document.getElementById("retrievedInputsDiv"); + testCaseListElement.style.visibility = "hidden"; +} + window.onload = () => { renderTestCasesList(); @@ -47,4 +53,7 @@ window.onload = () => { }; renderCard(card, cardRenderedCallback); + + const hideInputsButton = document.getElementById("hideInputsBtn"); + hideInputsButton.click = () => { hideInputsDiv(); }; } diff --git a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts index 060504c6ab..26c64c9564 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts @@ -3,7 +3,9 @@ const v10TestCases: string[] = ['Scenarios/ActivityUpdate']; const v11TestCases: string[] = []; const v12TestCases: string[] = []; -const v13TestCases: string[] = ['Elements/Input.Text.ErrorMessage']; +const v13TestCases: string[] = [ + 'Elements/Input.Text.ErrorMessage', 'Elements/Input.Number.ErrorMessage' +]; const v14TestCases: string[] = []; const v15TestCases: string[] = []; const v16TestCases: string[] = [ diff --git a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts index 682e0b6777..d323c8e90c 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts @@ -33,8 +33,8 @@ export function listAllFiles(): HTMLLIElement[] { } export async function readAndRenderCard(fileName: string, callbackFunction: Function) { - const retrievedInputsDiv: HTMLElement = document.getElementById("renderedCardSpace"); - retrievedInputsDiv.style.visibility = "hidden"; + const renderedCardSpaceDiv: HTMLElement = document.getElementById("renderedCardSpace"); + renderedCardSpaceDiv.style.visibility = "hidden"; const response = await fetch(`samples/${fileName}`); @@ -59,8 +59,6 @@ export async function readAndRenderCard(fileName: string, callbackFunction: Func } renderCard(jsonToRender, callbackFunction); - - retrievedInputsDiv.style.visibility = "visible"; } export function renderCard(cardJson: any, callbackFunction: Function): void { @@ -98,6 +96,7 @@ export function renderCard(cardJson: any, callbackFunction: Function): void { const retrievedInputsDiv: HTMLElement = document.getElementById("retrievedInputsDiv"); retrievedInputsDiv.innerHTML = inputsAsJson; + retrievedInputsDiv.style.visibility = "visible"; }; // For markdown support you need a third-party library @@ -122,4 +121,5 @@ export function cardRenderedCallback(renderedCard: HTMLElement) { const renderedCardDiv = document.getElementById("renderedCardSpace"); renderedCardDiv.innerHTML = ""; renderedCardDiv.appendChild(renderedCard); + renderedCardDiv.style.visibility = "visible"; } diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts new file mode 100644 index 0000000000..d61c27bd14 --- /dev/null +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { WebElement, By } from "selenium-webdriver"; +import { TestUtils } from "./test-utils"; +import * as Assert from "assert"; + +export class Element { + underlyingElement?: WebElement = undefined; + + protected constructor() {} + + elementWasFound(): boolean + { + return (this.underlyingElement !== undefined); + } +} + +export abstract class Input extends Element { + protected constructor() { + super(); + } + + abstract setData(data: string): Promise; +} + +export class InputText extends Input { + private constructor(){ + super(); + } + + static async getInputWithId(id: string): Promise + { + let input = new InputText(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-textInput"); + return input; + } + + override async setData(data: string): Promise + { + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); + } + + async isFocused(): Promise + { + if (this.elementWasFound()) + { + const inputId: string = await this.underlyingElement!.getAttribute("id"); + const activeElementId: string = await TestUtils.getInstance().getDriver().switchTo().activeElement().getAttribute("id"); + + return (inputId === activeElementId); + } + + return false; + } +} + +export class InputDate extends Input { + private constructor(){ + super(); + } + + static async getInputWithId(id: string): Promise + { + let input = new InputDate(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput"); + return input; + } + + override async setData(data: string): Promise + { + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); + } +} + +export class InputNumber extends Input { + + private constructor(){ + super(); + } + + static async getInputWithId(id: string): Promise + { + let input = new InputNumber(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput"); + return input; + } + + override async setData(data: string): Promise { + throw new Error("Method not implemented."); + } + +} + +export class Action extends Element { + private constructor() + { + super(); + } + + static async getActionWithTitle(title: string): Promise + { + let action = new Action(); + action.underlyingElement = await TestUtils.getInstance().tryGetActionWithTitle(title); + return action; + } + + async click() + { + await TestUtils.getInstance().hideInputsDiv(); + await this.underlyingElement?.click(); + } +} + +// As currently there's only one carousel per card is supported then this tests are simpler +export class Carousel { + + private constructor(){} + + static async clickOnLeftArrow() + { + const leftArrow = await TestUtils.getInstance().getDriver().findElement(By.className("ac-carousel-left")); + await leftArrow.click(); + } + + static async clickOnRightArrow() + { + const leftArrow = await TestUtils.getInstance().getDriver().findElement(By.className("ac-carousel-right")); + await leftArrow.click(); + } + + static async isPageVisible(pageId: string): Promise + { + let anyPageIsVisible = false; + + // Due to how the swiper library is made, the first and last pages are duplicated, if we were to use the regular + // getElementWithId method we would retrieve the duplicated slide, so we have to get the second element with that id + const pages = await TestUtils.getInstance().getElementsWithId(pageId); + + for (const page of pages) + { + const pageVisibility = await TestUtils.getInstance().getCssPropertyValueForElement(page, "visibility"); + anyPageIsVisible = anyPageIsVisible || (pageVisibility === "visible") + } + + return anyPageIsVisible; + } + + static async getPageDirection(pageId: string): Promise + { + const pageElement = await TestUtils.getInstance().getElementWithId(pageId); + // The actual direction is provided to a id-less div under the page, so + // we query for that value + const pageContainer = await pageElement.findElement(By.xpath("./*")); + const pageDirection = await pageContainer.getAttribute("dir"); + + return pageDirection; + } + + static async waitForAnimationsToEnd(): Promise + { + await TestUtils.getInstance().delay(1000); + } +} \ No newline at end of file diff --git a/source/nodejs/tests/ui-tests/src/selenium-utils.ts b/source/nodejs/tests/ui-tests/src/selenium-utils.ts new file mode 100644 index 0000000000..5a7f16910c --- /dev/null +++ b/source/nodejs/tests/ui-tests/src/selenium-utils.ts @@ -0,0 +1,2 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. \ No newline at end of file diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts new file mode 100644 index 0000000000..e39281a6cf --- /dev/null +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { Builder, By, Capabilities, until, WebDriver, WebElement } from "selenium-webdriver"; +import * as Assert from "assert"; +import { WaitUntil } from "./wait-utils"; +import { elementIsVisible } from "selenium-webdriver/lib/until"; + +export class TestUtils { + timeoutForCardRendering: number = 10000; + private driver: WebDriver; + private hideInputsButton: WebElement | undefined = undefined; + private inputs: any = undefined; + + private static instance : TestUtils; + + private constructor() + { + this.driver = new Builder().withCapabilities(Capabilities.edge()).build(); + } + + async initializeDriver() + { + await TestUtils.instance.driver.get("http://127.0.0.1:8080/"); + } + + async stopDriver() + { + if (this.driver) { + await this.driver.quit(); + } + } + + static getInstance() + { + if (TestUtils.instance == undefined){ + TestUtils.instance = new TestUtils(); + } + return TestUtils.instance; + } + + getDriver() + { + return this.driver; + } + + async goToTestCase(testCaseName: string): Promise { + const elementLinkText: WebElement = await this.driver.findElement(By.id(testCaseName)); + await elementLinkText.click(); + + const renderedCardContainer: WebElement = await this.getElementWithId("renderedCardSpace"); + + await this.driver.wait(until.elementIsVisible(renderedCardContainer), this.timeoutForCardRendering); + } + + async getHideInputsButton(): Promise + { + if (this.hideInputsButton === undefined) + { + this.hideInputsButton = await this.getElementWithId("hideInputsBtn"); + } + + return this.hideInputsButton!; + } + + async hideInputsDiv() : Promise + { + this.inputs = undefined; + await (await this.getHideInputsButton()).click(); + } + + async getInputFor(inputId: string): Promise { + const inputs = await this.getRetrievedInputs(); + return inputs[inputId]; + } + + async getRetrievedInputs(): Promise { + if (this.inputs === undefined) + { + const retrievedInputsDiv: WebElement = await this.getElementWithId("retrievedInputsDiv"); + await this.driver.wait(until.elementIsVisible(retrievedInputsDiv), this.timeoutForCardRendering); + + const retrievedInputs: string = await retrievedInputsDiv.getText(); + + this.inputs = JSON.parse(retrievedInputs) + } + + return this.inputs; + } + + async tryGetActionWithTitle(actionTitle: string): Promise { + const buttonList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-label='${actionTitle}']`)); + + return (buttonList.length > 0) ? buttonList[0] : undefined; + } + + async getActionWithTitle(actionTitle: string): Promise { + const button: WebElement = await this.driver.findElement(By.xpath(`//*[@title='${actionTitle}']`)); + + return button; + } + + async clickOnActionWithTitle(actionTitle: string): Promise { + const button: WebElement = await this.getActionWithTitle(actionTitle); + await button.click(); + } + + async waitUntilElementIsCssVisible(id: string, timeoutInMs?: number) + { + const elementsToWaitFor: WebElement[] = await this.getElementsWithId(id); + const timeOut = timeoutInMs !== undefined ? timeoutInMs : this.timeoutForCardRendering; + + let successFunction = async (params: any) => { + let elementIsVisible: boolean = false; + + for (let element of elementsToWaitFor) + { + let visibility = await TestUtils.getInstance().getCssPropertyValueForElement(element, "visibility"); + elementIsVisible = elementIsVisible || (visibility === "visible"); + } + + return elementIsVisible; + }; + + await WaitUntil.customWait(timeOut, successFunction, elementsToWaitFor) + } + + async waitUntilElementIsVisible(id: string) { + const elementToWaitFor: WebElement = await this.getElementWithId(id); + await this.driver.wait(until.elementIsVisible(elementToWaitFor), this.timeoutForCardRendering); + } + + async waitUntilElementIsNotVisible(id: string) { + const elementToWaitFor: WebElement = await this.getElementWithId(id); + await this.driver.wait(until.elementIsNotVisible(elementToWaitFor), this.timeoutForCardRendering); + } + + + + async getInput(inputId: string, className: string): Promise { + const inputDiv: WebElement = await this.driver.findElement(By.id(inputId)); + const input = await inputDiv.findElement(By.className(className)); + + return input; + } + + async setValueOnInput(inputId: string, className: string, value: string) { + const input: WebElement = await this.getInput(inputId, className); + await input.click(); + await input.sendKeys(value); + } + + async setDateOnDateInput(dateInputId: string, value: string): Promise { + await this.setValueOnInput(dateInputId, "ac-dateInput", value); + } + + async getTextInput(textInputId: string): Promise { + const textInput: WebElement = await this.getInput(textInputId, "ac-textInput"); + + return textInput; + } + + // Await for x miliseconds + async delay(miliseconds: number): Promise { + return new Promise(res => setTimeout(res, miliseconds)); + } + + async assertElementWithIdDoesNotExist(id: string): Promise { + const elementList = await this.driver.findElements(By.id(id)); + Assert.strictEqual(0, elementList.length); + } + + async getElementsWithId(id: string): Promise { + return this.driver.findElements(By.id(id)); + } + + async getElementWithId(id: string): Promise { + return this.driver.findElement(By.id(id)); + } + + async getCssPropertyValueForElement(element: WebElement, cssProperty: string): Promise { + return element.getCssValue(cssProperty); + } + + async getCssPropertyValueForElementWithId(id: string, cssProperty: string): Promise { + const element: WebElement = await this.getElementWithId(id); + + return this.getCssPropertyValueForElement(element, cssProperty); + } +} diff --git a/source/nodejs/tests/ui-tests/src/testUtils.ts b/source/nodejs/tests/ui-tests/src/testUtils.ts deleted file mode 100644 index 9d878efbdf..0000000000 --- a/source/nodejs/tests/ui-tests/src/testUtils.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as Webdriver from "selenium-webdriver"; -import * as Assert from "assert"; - -export class TestUtils { - driver: Webdriver.WebDriver; - timeoutForCardRendering: number = 10000; - - constructor(driver: Webdriver.WebDriver) { - this.driver = driver; - } - - async goToTestCase(testCaseName: string): Promise { - const elementLinkText: Webdriver.WebElement = await this.driver.findElement(Webdriver.By.id(testCaseName)); - await elementLinkText.click(); - - const renderedCardContainer: Webdriver.WebElement = await this.getElementWithId("renderedCardSpace"); - - await this.driver.wait(Webdriver.until.elementIsVisible(renderedCardContainer), this.timeoutForCardRendering); - } - - async tryGetActionWithTitle(actionTitle: string): Promise { - const buttonList: Webdriver.WebElement[] = await this.driver.findElements(Webdriver.By.xpath(`//*[@aria-label='${actionTitle}']`)); - - return (buttonList.length > 0) ? buttonList[0] : null; - } - - async getActionWithTitle(actionTitle: string): Promise { - const button: Webdriver.WebElement = await this.driver.findElement(Webdriver.By.xpath(`//*[@title='${actionTitle}']`)); - - return button; - } - - async clickOnActionWithTitle(actionTitle: string): Promise { - const button: Webdriver.WebElement = await this.getActionWithTitle(actionTitle); - await button.click(); - } - - async getRetrievedInputs(): Promise { - const retrievedInputsDiv: Webdriver.WebElement = await this.driver.findElement(Webdriver.By.id("retrievedInputsDiv")); - const retrievedInputsText: string = await retrievedInputsDiv.getText(); - - return retrievedInputsText; - } - - async getInputFor(inputId: string): Promise { - const retrievedInputsAsJsonString: string = await this.getRetrievedInputs(); - const retrievedInputs: any = JSON.parse(retrievedInputsAsJsonString); - - return retrievedInputs[inputId]; - } - - async getInput(inputId: string, className: string): Promise { - const inputDiv: Webdriver.WebElement = await this.driver.findElement(Webdriver.By.id(inputId)); - const input = await inputDiv.findElement(Webdriver.By.className(className)); - - return input; - } - - async setValueOnInput(inputId: string, className: string, value: string) { - const input: Webdriver.WebElement = await this.getInput(inputId, className); - await input.click(); - await input.sendKeys(value); - } - - async setDateOnDateInput(dateInputId: string, value: string): Promise { - await this.setValueOnInput(dateInputId, "ac-dateInput", value); - } - - async setTextOnTextInput(textInputId: string, value: string): Promise { - await this.setValueOnInput(textInputId, "ac-textInput", value); - } - - async getTextInput(textInputId: string): Promise { - const textInput: Webdriver.WebElement = await this.getInput(textInputId, "ac-textInput"); - - return textInput; - } - - // Await for x miliseconds - async delay(miliseconds: number): Promise { - return new Promise(res => setTimeout(res, miliseconds)); - } - - async assertElementWithIdDoesNotExist(id: string): Promise { - const elementList = await this.driver.findElements(Webdriver.By.id(id)); - Assert.strictEqual(0, elementList.length); - } - - async getElementsWithId(id: string): Promise { - return this.driver.findElements(Webdriver.By.id(id)); - } - - async getElementWithId(id: string): Promise { - return this.driver.findElement(Webdriver.By.id(id)); - } - - async getCssPropertyValueForElement(element: Webdriver.WebElement, cssProperty: string): Promise { - return element.getCssValue(cssProperty); - } - - async getCssPropertyValueForElementWithId(id: string, cssProperty: string): Promise { - const element: Webdriver.WebElement = await this.getElementWithId(id); - - return this.getCssPropertyValueForElement(element, cssProperty); - } -} diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index 1890fdcd4c..349a3f54cd 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -2,19 +2,20 @@ // Licensed under the MIT License. import * as Assert from "assert"; import { assert } from "console"; -import * as Webdriver from "selenium-webdriver"; -import * as TestUtils from "./testUtils"; +import { util } from "prettier"; +import { By, WebElement } from "selenium-webdriver"; +import { Action, Carousel, InputDate, InputText } from "./card-element-utils"; +import { TestUtils } from "./test-utils"; describe("Mock function", function() { - let driver: Webdriver.WebDriver; - let testUtils: TestUtils.TestUtils; + let utils: TestUtils; // This is a constant value for the wait time between pressing an action and retrieving // the input value. Only use this if you see some test flakiness. Value is given in ms const delayForInputRetrieval: number = 500; const delayForCarouselArrows: number = 1000; const delayForCarouselTimer: number = 5500; - const timeOutValueForCarousel: number = 9000; + const timeOutValueForCarousel: number = 30000; const timeOutValueForSuddenJumpTest: number = 20000; // Timeout of 10 minutes for the dev server to start up in the CI jobs, the dev-server @@ -23,184 +24,174 @@ describe("Mock function", function() { const timeoutForServerStartupInCIBuild: number = 600000; beforeAll(async() => { - driver = new Webdriver.Builder().withCapabilities(Webdriver.Capabilities.edge()).build(); - await driver.get("http://127.0.0.1:8080/"); - - testUtils = new TestUtils.TestUtils(driver); + utils = TestUtils.getInstance(); + utils.initializeDriver(); }, timeoutForServerStartupInCIBuild); afterAll(async() => { - if (driver) { - await driver.quit(); - } + await utils.stopDriver(); }); test("Test ActivityUpdate submit", (async() => { - await testUtils.goToTestCase("v1.0/ActivityUpdate"); + await utils.goToTestCase("v1.0/ActivityUpdate"); - await testUtils.clickOnActionWithTitle("Set due date"); + await utils.clickOnActionWithTitle("Set due date"); - await testUtils.setDateOnDateInput("dueDate", "02041993"); - await testUtils.setTextOnTextInput("comment", "A comment"); + let dueDateInput = await InputDate.getInputWithId("dueDate"); + await dueDateInput.setData("02041993"); - await testUtils.clickOnActionWithTitle("OK"); + let commentInput = await InputText.getInputWithId("comment"); + await commentInput.setData("A comment"); - const dueDateRetrievedValue: string = await testUtils.getInputFor("dueDate"); - Assert.strictEqual("1993-02-04", dueDateRetrievedValue); + let okAction = await Action.getActionWithTitle("OK"); + await okAction.click(); - const commentRetrievedValue: string = await testUtils.getInputFor("comment"); - Assert.strictEqual("A comment", commentRetrievedValue); + const dueDateRetrievedValue: string = await utils.getInputFor("dueDate"); + Assert.strictEqual(dueDateRetrievedValue, "1993-02-04"); + + const commentRetrievedValue: string = await utils.getInputFor("comment"); + Assert.strictEqual(commentRetrievedValue, "A comment"); })); test("Test TextInput get focus on invalid submit", (async() => { - await testUtils.goToTestCase("v1.3/Input.Text.ErrorMessage"); + await utils.goToTestCase("v1.3/Input.Text.ErrorMessage"); + + let commentInput = await InputText.getInputWithId("id1"); - await testUtils.clickOnActionWithTitle("Submit"); + let commentInputIsFocused = await commentInput.isFocused() - const firstInput: Webdriver.WebElement = await testUtils.getTextInput("id1"); + Assert.strictEqual(commentInputIsFocused, false); - const firstInputId: string = await firstInput.getAttribute("id"); - const activeElementId: string = await driver.switchTo().activeElement().getAttribute("id"); + let submitAction = await Action.getActionWithTitle("Submit"); + await submitAction.click(); - Assert.strictEqual(firstInputId, activeElementId); + commentInputIsFocused = await commentInput.isFocused() + + Assert.strictEqual(commentInputIsFocused, true); })); test("Test actions are rendered and active below carousel", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.HostConfig"); - - await testUtils.clickOnActionWithTitle("See more"); + await utils.goToTestCase("v1.6/Carousel.HostConfig"); - await testUtils.delay(delayForInputRetrieval); + let seeMoreAction = await Action.getActionWithTitle("See more"); + await seeMoreAction.click(); - const url: string = await testUtils.getInputFor("url"); - Assert.strictEqual("https://adaptivecards.io", url); + const url: string = await utils.getInputFor("url"); + Assert.strictEqual(url, "https://adaptivecards.io"); })); test("Test page limit is honoured", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.HostConfig"); + await utils.goToTestCase("v1.6/Carousel.HostConfig"); - await testUtils.assertElementWithIdDoesNotExist("page10"); + await utils.assertElementWithIdDoesNotExist("page10"); })); test("Unsupported elements are not rendered", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.ForbiddenElements"); - - await testUtils.assertElementWithIdDoesNotExist("id1"); - await testUtils.assertElementWithIdDoesNotExist("id2"); - await testUtils.assertElementWithIdDoesNotExist("id3"); - await testUtils.assertElementWithIdDoesNotExist("id4"); - await testUtils.assertElementWithIdDoesNotExist("id5"); - await testUtils.assertElementWithIdDoesNotExist("id6"); - await testUtils.assertElementWithIdDoesNotExist("id7"); + await utils.goToTestCase("v1.6/Carousel.ForbiddenElements"); + + await utils.assertElementWithIdDoesNotExist("id1"); + await utils.assertElementWithIdDoesNotExist("id2"); + await utils.assertElementWithIdDoesNotExist("id3"); + await utils.assertElementWithIdDoesNotExist("id4"); + await utils.assertElementWithIdDoesNotExist("id5"); + await utils.assertElementWithIdDoesNotExist("id6"); + await utils.assertElementWithIdDoesNotExist("id7"); })); test("Verify left and right buttons in carousel work", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.ScenarioCards"); + await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); - let firstCarouselPageVisibility: string = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("visible", firstCarouselPageVisibility); + let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); - const rightArrow = await testUtils.driver.findElement(Webdriver.By.className("ac-carousel-right")); - await rightArrow.click(); + await Carousel.clickOnRightArrow(); - await testUtils.delay(delayForCarouselArrows); + await utils.waitUntilElementIsCssVisible("theSecondCarouselPage", 6000); - const secondCarouselPageVisibility: string = - await testUtils.getCssPropertyValueForElementWithId("theSecondCarouselPage", "visibility"); - Assert.strictEqual("visible", secondCarouselPageVisibility); + let secondPageIsVisible = await Carousel.isPageVisible("theSecondCarouselPage"); + Assert.strictEqual(secondPageIsVisible, true); - const leftArrow = await testUtils.driver.findElement(Webdriver.By.className("ac-carousel-left")); - await leftArrow.click(); + await Carousel.waitForAnimationsToEnd(); + await Carousel.clickOnLeftArrow(); - await testUtils.delay(delayForCarouselArrows); + await utils.waitUntilElementIsCssVisible("firstCarouselPage"); - firstCarouselPageVisibility = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("visible", firstCarouselPageVisibility); - })); + firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); + }), timeOutValueForSuddenJumpTest); test("Unsupported actions are not rendered", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.ForbiddenActions"); + await utils.goToTestCase("v1.6/Carousel.ForbiddenActions"); - const showCardAction = await testUtils.tryGetActionWithTitle("Action.ShowCard"); - Assert.strictEqual(null, showCardAction); + let showCardAction = await Action.getActionWithTitle("Action.ShowCard"); + Assert.strictEqual(showCardAction.elementWasFound(), false); - const toggleVisibilityAction = await testUtils.tryGetActionWithTitle("Action.ToggleVisibility"); - Assert.strictEqual(null, toggleVisibilityAction); + let toggleVisibilityAction = await Action.getActionWithTitle("Action.ToggleVisibility"); + Assert.strictEqual(toggleVisibilityAction.elementWasFound(), false); })); // Giving this test 7 seconds to run test("Test autoplay is disabled", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.ScenarioCards"); + await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); - let firstCarouselPageVisibility: string = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("visible", firstCarouselPageVisibility); + let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); // Await for 5 seconds and verify no change happened - await testUtils.delay(5000); + await utils.delay(5000); - firstCarouselPageVisibility = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("visible", firstCarouselPageVisibility); + firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); }), 7000); // Giving this test 9 seconds to run test("Test autoplay is applied", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.ScenarioCards.Timer"); - - let firstCarouselPageVisibility: string = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); + await utils.goToTestCase("v1.6/Carousel.ScenarioCards.Timer"); - Assert.strictEqual("visible", firstCarouselPageVisibility); + let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); // Await for 5 seconds and verify the first page is now hidden - await testUtils.delay(7000); + await utils.waitUntilElementIsNotVisible("firstCarouselPage"); - firstCarouselPageVisibility = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("hidden", firstCarouselPageVisibility); + firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, false); - const secondCarouselPageVisibility: string = - await testUtils.getCssPropertyValueForElementWithId("theSecondCarouselPage", "visibility"); - Assert.strictEqual("visible", secondCarouselPageVisibility); + let secondPageIsVisible = await Carousel.isPageVisible("theSecondCarouselPage"); + Assert.strictEqual(secondPageIsVisible, true); }), timeOutValueForCarousel); test("Test click on navigation does not cause sudden jump", (async() => { - await testUtils.goToTestCase("v1.6/Carousel"); - - let firstCarouselPageVisibility: string = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); + await utils.goToTestCase("v1.6/Carousel"); - Assert.strictEqual("visible", firstCarouselPageVisibility); + let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, true); // wait for 2 pages to turn - await testUtils.delay(delayForCarouselTimer * 2); + await utils.waitUntilElementIsCssVisible("last-carousel-page", 12000); - firstCarouselPageVisibility = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("hidden", firstCarouselPageVisibility); + firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, false); - // Due to how the swiper library is made, the first and last pages are duplicated, if we were to use the regular - // getElementWithId method we would retrieve the duplicated slide, so we have to get the second element with that id - - const lastPages = await testUtils.getElementsWithId("last-carousel-page"); - Assert.strictEqual(lastPages.length, 2); - - const thirdCarouselPageVisibility: string = - await testUtils.getCssPropertyValueForElement(lastPages[1], "visibility"); - Assert.strictEqual("visible", thirdCarouselPageVisibility); + let lastPageIsVisible = await Carousel.isPageVisible("last-carousel-page"); + Assert.strictEqual(lastPageIsVisible, true); // cause the page to go the 2nd page - const leftArrow = await testUtils.driver.findElement(Webdriver.By.className("ac-carousel-left")); - await leftArrow.click(); - await testUtils.delay(delayForCarouselArrows); + await Carousel.waitForAnimationsToEnd(); + await Carousel.clickOnLeftArrow(); + + await utils.waitUntilElementIsVisible("theSecondCarouselPage"); // make sure firstCarouselPage is hidden - firstCarouselPageVisibility = await testUtils.getCssPropertyValueForElementWithId("firstCarouselPage", "visibility"); - Assert.strictEqual("hidden", firstCarouselPageVisibility); + firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + Assert.strictEqual(firstPageIsVisible, false); }), timeOutValueForSuddenJumpTest); test("Test rtl on carousel", (async() => { - await testUtils.goToTestCase("v1.6/Carousel.rtl"); + await utils.goToTestCase("v1.6/Carousel.rtl"); for (const page of [["firstCarouselPage", "rtl"], ["secondCarouselPage", "ltr"], ["thirdCarouselPage", "rtl"]]){ - const pageElement = await testUtils.getElementWithId(page[0]); - const pageContainer = pageElement.findElement(Webdriver.By.xpath("./*")); - const pageDirection: string = await pageContainer.getAttribute("dir"); + const pageDirection = await Carousel.getPageDirection(page[0]) Assert.strictEqual(pageDirection, page[1]); } diff --git a/source/nodejs/tests/ui-tests/src/wait-utils.ts b/source/nodejs/tests/ui-tests/src/wait-utils.ts new file mode 100644 index 0000000000..800d84ae84 --- /dev/null +++ b/source/nodejs/tests/ui-tests/src/wait-utils.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestUtils } from "./test-utils"; + +export class WaitUntil +{ + + static async customWait(timeoutMs: number, successFunction: Function, params: any) : Promise + { + let milisPassed : number = 0; + + while (milisPassed < timeoutMs) + { + let successFunctionResult = await successFunction(params); + if (successFunctionResult === true) + { + break; + } + milisPassed += 100; + await TestUtils.getInstance().delay(100); + } + } +} \ No newline at end of file From e2d4fedc47b1ad1457b77629508b19d7215bf2c0 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Mon, 3 Jan 2022 09:44:30 -0800 Subject: [PATCH 02/12] Fix more tests --- .../src/file-retriever-utils.ts | 3 +- .../tests/ui-tests/src/card-element-utils.ts | 27 ++++++++++++++++-- .../nodejs/tests/ui-tests/src/test-utils.ts | 5 ++++ .../tests/ui-tests/src/ui-tests.test.ts | 28 +++++++++++++++---- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts index 26c64c9564..00a9982f43 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts @@ -4,7 +4,8 @@ const v10TestCases: string[] = ['Scenarios/ActivityUpdate']; const v11TestCases: string[] = []; const v12TestCases: string[] = []; const v13TestCases: string[] = [ - 'Elements/Input.Text.ErrorMessage', 'Elements/Input.Number.ErrorMessage' + 'Elements/Input.Text.ErrorMessage', 'Elements/Input.Number.ErrorMessage', + 'Elements/Input.ChoiceSet.ErrorMessage' ]; const v14TestCases: string[] = []; const v15TestCases: string[] = []; diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index d61c27bd14..2963899a12 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -83,14 +83,33 @@ export class InputNumber extends Input { static async getInputWithId(id: string): Promise { let input = new InputNumber(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput"); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-numberInput"); + return input; + } + + override async setData(data: string): Promise { + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); + } +} + +export class InputChoiceSet extends Input +{ + private constructor() { + super(); + } + + static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean): Promise + { + let input = new InputChoiceSet(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-choiceSetInput-expanded"); return input; } override async setData(data: string): Promise { - throw new Error("Method not implemented."); + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); } - } export class Action extends Element { @@ -158,6 +177,8 @@ export class Carousel { return pageDirection; } + // This is a helper method to handle the odd scenario of the carousel not behaving correctly + // when clicking an arrow while the carousel is moving static async waitForAnimationsToEnd(): Promise { await TestUtils.getInstance().delay(1000); diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts index e39281a6cf..bdc9acb89d 100644 --- a/source/nodejs/tests/ui-tests/src/test-utils.ts +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -186,4 +186,9 @@ export class TestUtils { return this.getCssPropertyValueForElement(element, cssProperty); } + + async getElementsWithName(id: string): Promise + { + return await this.driver.findElements(By.name(id)); + } } diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index 349a3f54cd..70026e982f 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -12,9 +12,7 @@ describe("Mock function", function() { // This is a constant value for the wait time between pressing an action and retrieving // the input value. Only use this if you see some test flakiness. Value is given in ms - const delayForInputRetrieval: number = 500; - const delayForCarouselArrows: number = 1000; - const delayForCarouselTimer: number = 5500; + const delayForCarouselTimer: number = 6000; const timeOutValueForCarousel: number = 30000; const timeOutValueForSuddenJumpTest: number = 20000; @@ -70,6 +68,24 @@ describe("Mock function", function() { Assert.strictEqual(commentInputIsFocused, true); })); + test("Test interaction with Input.ChoiceSet", (async() => { + await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); + + let compactChoiceSet = await utils.getElementWithId("requiredCompactId"); + let actualElement = await compactChoiceSet.findElement(By.className("ac-choiceSetInput-compact")); + await actualElement.click(); + + await (await actualElement.findElement(By.xpath(`//*[@aria-label='Option 1']`))).click(); + + let expandedChoiceSet = await utils.getElementsWithName("requiredExpandedId"); + Assert.strictEqual(await expandedChoiceSet[0].getAttribute("value"), "1"); + Assert.strictEqual(await expandedChoiceSet[1].getAttribute("value"), "2"); + + let multiselectChoiceSet = await utils.getElementsWithName("requiredMultiselectId"); + Assert.strictEqual(await multiselectChoiceSet[0].getAttribute("value"), "1"); + Assert.strictEqual(await multiselectChoiceSet[1].getAttribute("value"), "2"); + })); + test("Test actions are rendered and active below carousel", (async() => { await utils.goToTestCase("v1.6/Carousel.HostConfig"); @@ -106,7 +122,7 @@ describe("Mock function", function() { await Carousel.clickOnRightArrow(); - await utils.waitUntilElementIsCssVisible("theSecondCarouselPage", 6000); + await utils.waitUntilElementIsCssVisible("theSecondCarouselPage", delayForCarouselTimer); let secondPageIsVisible = await Carousel.isPageVisible("theSecondCarouselPage"); Assert.strictEqual(secondPageIsVisible, true); @@ -168,7 +184,7 @@ describe("Mock function", function() { Assert.strictEqual(firstPageIsVisible, true); // wait for 2 pages to turn - await utils.waitUntilElementIsCssVisible("last-carousel-page", 12000); + await utils.waitUntilElementIsCssVisible("last-carousel-page", delayForCarouselTimer * 2); firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, false); @@ -180,7 +196,7 @@ describe("Mock function", function() { await Carousel.waitForAnimationsToEnd(); await Carousel.clickOnLeftArrow(); - await utils.waitUntilElementIsVisible("theSecondCarouselPage"); + await utils.waitUntilElementIsCssVisible("theSecondCarouselPage"); // make sure firstCarouselPage is hidden firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); From a079a62e520ca60cd0202d8389cc0ab96f76d393 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Mon, 3 Jan 2022 14:23:17 -0800 Subject: [PATCH 03/12] Add support for more elements --- .../src/file-retriever-utils.ts | 10 +- .../src/rendering-utils.ts | 12 +- .../tests/ui-tests/src/card-element-utils.ts | 165 +++++++++++++++++- .../nodejs/tests/ui-tests/src/test-utils.ts | 31 ++-- .../tests/ui-tests/src/ui-tests.test.ts | 77 ++++++-- 5 files changed, 263 insertions(+), 32 deletions(-) diff --git a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts index 00a9982f43..8de721bf0f 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -const v10TestCases: string[] = ['Scenarios/ActivityUpdate']; +const v10TestCases: string[] = ['Scenarios/ActivityUpdate', + 'Elements/Container.SelectAction']; const v11TestCases: string[] = []; const v12TestCases: string[] = []; const v13TestCases: string[] = [ - 'Elements/Input.Text.ErrorMessage', 'Elements/Input.Number.ErrorMessage', - 'Elements/Input.ChoiceSet.ErrorMessage' + 'Elements/Input.Text.ErrorMessage', + 'Elements/Input.Number.ErrorMessage', + 'Elements/Input.ChoiceSet.ErrorMessage', + 'Elements/Input.Time.ErrorMessage', + 'Elements/Input.Toggle.ErrorMessage' ]; const v14TestCases: string[] = []; const v15TestCases: string[] = []; diff --git a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts index d323c8e90c..0935de4fe1 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { getTestCasesList } from "./file-retriever-utils"; -import { Action, AdaptiveCard, ExecuteAction, HostConfig, IMarkdownProcessingResult, Input, OpenUrlAction, SerializationContext, SubmitAction, Version, Versions } from "adaptivecards"; +import { Action, AdaptiveCard, ExecuteAction, HostConfig, IMarkdownProcessingResult, Input, OpenUrlAction, PropertyBag, SerializationContext, SubmitAction, Version, Versions } from "adaptivecards"; import * as Remarkable from "remarkable"; export function listAllFiles(): HTMLLIElement[] { @@ -87,6 +87,16 @@ export function renderCard(cardJson: any, callbackFunction: Function): void { inputsMap[input.id] = input.value; }); + if (actionType === SubmitAction.JsonTypeName) + { + const submitAction: SubmitAction = action as SubmitAction; + + for (let [key, value] of Object.entries(submitAction.data)) + { + inputsMap[key] = value; + } + } + inputsAsJson = JSON.stringify(inputsMap); } else if (actionType === OpenUrlAction.JsonTypeName) { diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 2963899a12..ace4120494 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -74,6 +74,26 @@ export class InputDate extends Input { } } +export class InputTime extends Input { + + private constructor(){ + super(); + } + + static async getInputWithId(id: string): Promise + { + let input = new InputTime(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-timeInput"); + return input; + } + + override async setData(data: string): Promise { + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); + } + +} + export class InputNumber extends Input { private constructor(){ @@ -95,20 +115,149 @@ export class InputNumber extends Input { export class InputChoiceSet extends Input { - private constructor() { + isExpanded: boolean = false; + isMultiSelect: boolean = false; + underlyingElements: WebElement[]; + + private constructor(isExpanded: boolean, isMultiSelect: boolean) { super(); + + this.isExpanded = isExpanded; + this.isMultiSelect = isMultiSelect; + } + + private async getCompactChoiceSet(id: string): Promise + { + let compactChoiceSet = await TestUtils.getInstance().getElementWithId(id); + let actualElement = await compactChoiceSet.findElement(By.className("ac-choiceSetInput-compact")); + return actualElement; + } + + private async getExpandedChoiceSet(id: string): Promise + { + return await TestUtils.getInstance().getElementsWithName(id); } static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean): Promise { - let input = new InputChoiceSet(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-choiceSetInput-expanded"); + let input = new InputChoiceSet(isExpanded, isMultiSelect); + + if (!isExpanded) + { + input.underlyingElement = await input.getCompactChoiceSet(id); + } + else + { + input.underlyingElements = await input.getExpandedChoiceSet(id); + } return input; } override async setData(data: string): Promise { + + if (this.elementWasFound()) { + if (this.isExpanded) { + if (this.isMultiSelect) { + const options: string[] = data.split(","); + for (const choice of this.underlyingElements) + { + const choiceValue = await choice.getAttribute("aria-label"); + const choiceIsSelected: boolean = await choice.isSelected(); + + // if the choice has to be selected and it's not selected, click it + // or if the choice must not be selected but is selected, click it + if ((options.includes(choiceValue) && !choiceIsSelected) || + (!options.includes(choiceValue) && choiceIsSelected)) { + await choice.click(); + } + } + } + else + { + for (const choice of this.underlyingElements) + { + const choiceValue = await choice.getAttribute("aria-label"); + if (choiceValue === data) { + await choice.click(); + } + } + } + + } else { + let underlyingElement: WebElement = this.underlyingElement!; + await underlyingElement.click(); + + let option = await underlyingElement.findElement(By.xpath(`//*[@aria-label='${data}']`)); + await option.click(); + } + } + } + + override elementWasFound(): boolean { + if (this.isExpanded) { + return this.underlyingElements.length > 0; + } + else { + return super.elementWasFound(); + } + } +} + +export class InputToggle extends Input { + private constructor(){ + super(); + } + + static async getInputWithId(id: string): Promise + { + let input = new InputToggle(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-toggleInput"); + return input; + } + + override async setData(data: string): Promise { + if (this.elementWasFound()) + { + const isInputSelected: boolean = await this.underlyingElement!.isSelected(); + let mustClick: boolean = true; + + if (data === "set") { + mustClick = !isInputSelected; + } else if (data === "unset") { + mustClick = isInputSelected; + } + + if (mustClick) + { + this.underlyingElement?.click(); + } + } + } +} + +export class Container extends Element +{ + private constructor() + { + super(); + } + + static async getContainer(id: string): Promise + { + return new Container(); + } + + static async getContainerWithAction(tooltip: string): Promise + { + let container = new Container(); + container.underlyingElement = await TestUtils.getInstance().tryGetContainerWithAction(tooltip); + return container; + } + + async click() + { + await TestUtils.getInstance().clearInputs(); await this.underlyingElement?.click(); - await this.underlyingElement?.sendKeys(data); } } @@ -118,6 +267,12 @@ export class Action extends Element { super(); } + static async clickOnActionWithTitle(title: string): Promise + { + let action: Action = await this.getActionWithTitle(title); + await action.click(); + } + static async getActionWithTitle(title: string): Promise { let action = new Action(); @@ -127,7 +282,7 @@ export class Action extends Element { async click() { - await TestUtils.getInstance().hideInputsDiv(); + await TestUtils.getInstance().clearInputs(); await this.underlyingElement?.click(); } } diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts index bdc9acb89d..9d56b42d04 100644 --- a/source/nodejs/tests/ui-tests/src/test-utils.ts +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -3,7 +3,6 @@ import { Builder, By, Capabilities, until, WebDriver, WebElement } from "selenium-webdriver"; import * as Assert from "assert"; import { WaitUntil } from "./wait-utils"; -import { elementIsVisible } from "selenium-webdriver/lib/until"; export class TestUtils { timeoutForCardRendering: number = 10000; @@ -62,18 +61,28 @@ export class TestUtils { return this.hideInputsButton!; } - async hideInputsDiv() : Promise + async clearInputs(): Promise { this.inputs = undefined; - await (await this.getHideInputsButton()).click(); + await this.hideInputsDiv(); + } + + async hideInputsDiv(): Promise + { + const hideInputsButton = await this.getHideInputsButton(); + await hideInputsButton.click(); } async getInputFor(inputId: string): Promise { - const inputs = await this.getRetrievedInputs(); - return inputs[inputId]; + if (this.inputs === undefined) + { + await this.getRetrievedInputs(); + } + + return this.inputs[inputId]; } - async getRetrievedInputs(): Promise { + async getRetrievedInputs(): Promise { if (this.inputs === undefined) { const retrievedInputsDiv: WebElement = await this.getElementWithId("retrievedInputsDiv"); @@ -83,8 +92,6 @@ export class TestUtils { this.inputs = JSON.parse(retrievedInputs) } - - return this.inputs; } async tryGetActionWithTitle(actionTitle: string): Promise { @@ -93,6 +100,12 @@ export class TestUtils { return (buttonList.length > 0) ? buttonList[0] : undefined; } + async tryGetContainerWithAction(tooltip: string): Promise { + const containerList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-description='${tooltip}']`)); + + return (containerList.length > 0) ? containerList[0] : undefined; + } + async getActionWithTitle(actionTitle: string): Promise { const button: WebElement = await this.driver.findElement(By.xpath(`//*[@title='${actionTitle}']`)); @@ -134,8 +147,6 @@ export class TestUtils { await this.driver.wait(until.elementIsNotVisible(elementToWaitFor), this.timeoutForCardRendering); } - - async getInput(inputId: string, className: string): Promise { const inputDiv: WebElement = await this.driver.findElement(By.id(inputId)); const input = await inputDiv.findElement(By.className(className)); diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index 70026e982f..c93a97f3b0 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -4,7 +4,7 @@ import * as Assert from "assert"; import { assert } from "console"; import { util } from "prettier"; import { By, WebElement } from "selenium-webdriver"; -import { Action, Carousel, InputDate, InputText } from "./card-element-utils"; +import { Action, Carousel, Container, InputChoiceSet, InputDate, InputText, InputTime, InputToggle } from "./card-element-utils"; import { TestUtils } from "./test-utils"; describe("Mock function", function() { @@ -41,8 +41,7 @@ describe("Mock function", function() { let commentInput = await InputText.getInputWithId("comment"); await commentInput.setData("A comment"); - let okAction = await Action.getActionWithTitle("OK"); - await okAction.click(); + await Action.clickOnActionWithTitle("OK"); const dueDateRetrievedValue: string = await utils.getInputFor("dueDate"); Assert.strictEqual(dueDateRetrievedValue, "1993-02-04"); @@ -71,19 +70,71 @@ describe("Mock function", function() { test("Test interaction with Input.ChoiceSet", (async() => { await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); - let compactChoiceSet = await utils.getElementWithId("requiredCompactId"); - let actualElement = await compactChoiceSet.findElement(By.className("ac-choiceSetInput-compact")); - await actualElement.click(); + let compactChoiceSet = await InputChoiceSet.getInputWithId("requiredCompactId", false, false); + await compactChoiceSet.setData("Option 1"); - await (await actualElement.findElement(By.xpath(`//*[@aria-label='Option 1']`))).click(); + let expandedChoiceSet = await InputChoiceSet.getInputWithId("requiredExpandedId", true, false); + await expandedChoiceSet.setData("Option 2"); - let expandedChoiceSet = await utils.getElementsWithName("requiredExpandedId"); - Assert.strictEqual(await expandedChoiceSet[0].getAttribute("value"), "1"); - Assert.strictEqual(await expandedChoiceSet[1].getAttribute("value"), "2"); + let multiselectChoiceSet = await InputChoiceSet.getInputWithId("requiredMultiselectId", true, true); + await multiselectChoiceSet.setData("Option 1,Option 2"); - let multiselectChoiceSet = await utils.getElementsWithName("requiredMultiselectId"); - Assert.strictEqual(await multiselectChoiceSet[0].getAttribute("value"), "1"); - Assert.strictEqual(await multiselectChoiceSet[1].getAttribute("value"), "2"); + await Action.clickOnActionWithTitle("OK"); + + const compactValue: string = await utils.getInputFor("requiredCompactId"); + Assert.strictEqual(compactValue, "1"); + + const expandedValue: string = await utils.getInputFor("requiredExpandedId"); + Assert.strictEqual(expandedValue, "2"); + + const multiselectValue: string = await utils.getInputFor("requiredMultiselectId"); + Assert.strictEqual(multiselectValue, "1,2"); + })); + + test("Test interaction with Input.Time", (async() => { + await utils.goToTestCase("v1.3/Input.Time.ErrorMessage"); + + let dueDateInput = await InputTime.getInputWithId("input1"); + await dueDateInput.setData("0109AM"); + + let commentInput = await InputTime.getInputWithId("input4"); + await commentInput.setData("0230PM"); + + await Action.clickOnActionWithTitle("OK"); + + const dueDateRetrievedValue: string = await utils.getInputFor("input1"); + Assert.strictEqual(dueDateRetrievedValue, "01:09"); + + const commentRetrievedValue: string = await utils.getInputFor("input4"); + Assert.strictEqual(commentRetrievedValue, "14:30"); + })); + + test("Test interaction with Input.Toggle", (async() => { + await utils.goToTestCase("v1.3/Input.Toggle.ErrorMessage"); + + let toggleInput = await InputToggle.getInputWithId("input2"); + await toggleInput.setData("set"); + + await Action.clickOnActionWithTitle("OK"); + + const dueDateRetrievedValue: string = await utils.getInputFor("input2"); + Assert.strictEqual(dueDateRetrievedValue, "true"); + })); + + test("Test container select action can be clicked", (async() => { + await utils.goToTestCase("v1.0/Container.SelectAction"); + + let submitContainer = await Container.getContainerWithAction("Submit action"); + await submitContainer.click(); + + const submitActionInfo: string = await utils.getInputFor("info"); + Assert.strictEqual(submitActionInfo, "My submit action data"); + + let emphasisContainer = await Container.getContainerWithAction("Go to a different url"); + await emphasisContainer.click(); + + const emphasisContainerUrl: string = await utils.getInputFor("url"); + Assert.strictEqual(emphasisContainerUrl, "https://msn.com"); })); test("Test actions are rendered and active below carousel", (async() => { From 82b8952c61f3d96f5647b9236dc4888e6cb9d7ab Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Mon, 3 Jan 2022 16:30:27 -0800 Subject: [PATCH 04/12] Add more tests for other elements --- .../v1.0/Elements/Column.SelectAction.json | 4 +- .../src/file-retriever-utils.ts | 7 +- .../tests/ui-tests/src/card-element-utils.ts | 8 +- .../tests/ui-tests/src/ui-tests.test.ts | 82 +++++++++++++++---- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/samples/v1.0/Elements/Column.SelectAction.json b/samples/v1.0/Elements/Column.SelectAction.json index 8b00d3bcab..7512e694ec 100644 --- a/samples/v1.0/Elements/Column.SelectAction.json +++ b/samples/v1.0/Elements/Column.SelectAction.json @@ -43,7 +43,7 @@ ], "selectAction": { "type": "Action.OpenUrl", - "tooltip": "cool link", + "tooltip": "another cool link", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } }, @@ -61,7 +61,7 @@ ], "selectAction": { "type": "Action.OpenUrl", - "tooltip": "cool link", + "tooltip": "a third cool link", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } } diff --git a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts index 8de721bf0f..07bbee9418 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -const v10TestCases: string[] = ['Scenarios/ActivityUpdate', - 'Elements/Container.SelectAction']; +const v10TestCases: string[] = [ + 'Scenarios/ActivityUpdate', + 'Elements/Container.SelectAction', + 'Elements/Column.SelectAction', + 'Elements/ColumnSet.SelectAction']; const v11TestCases: string[] = []; const v12TestCases: string[] = []; const v13TestCases: string[] = [ diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index ace4120494..0dee34398f 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -237,7 +237,7 @@ export class InputToggle extends Input { export class Container extends Element { - private constructor() + protected constructor() { super(); } @@ -261,6 +261,12 @@ export class Container extends Element } } +// Currently Column and ColumnSet behave just as container (except columnset having a different class name), +// leaving the empty classes for code clarity when used +export class Column extends Container {} + +export class ColumnSet extends Container {} + export class Action extends Element { private constructor() { diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index c93a97f3b0..5b29c9a7d7 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -4,7 +4,7 @@ import * as Assert from "assert"; import { assert } from "console"; import { util } from "prettier"; import { By, WebElement } from "selenium-webdriver"; -import { Action, Carousel, Container, InputChoiceSet, InputDate, InputText, InputTime, InputToggle } from "./card-element-utils"; +import { Action, Carousel, Column, ColumnSet, Container, InputChoiceSet, InputDate, InputNumber, InputText, InputTime, InputToggle } from "./card-element-utils"; import { TestUtils } from "./test-utils"; describe("Mock function", function() { @@ -33,7 +33,7 @@ describe("Mock function", function() { test("Test ActivityUpdate submit", (async() => { await utils.goToTestCase("v1.0/ActivityUpdate"); - await utils.clickOnActionWithTitle("Set due date"); + await Action.clickOnActionWithTitle("Set due date"); let dueDateInput = await InputDate.getInputWithId("dueDate"); await dueDateInput.setData("02041993"); @@ -59,14 +59,43 @@ describe("Mock function", function() { Assert.strictEqual(commentInputIsFocused, false); - let submitAction = await Action.getActionWithTitle("Submit"); - await submitAction.click(); + await Action.clickOnActionWithTitle("Submit"); commentInputIsFocused = await commentInput.isFocused() Assert.strictEqual(commentInputIsFocused, true); })); + test("Test interaction with Input.Number", (async() => { + await utils.goToTestCase("v1.3/Input.Number.ErrorMessage"); + + let input1 = await InputNumber.getInputWithId("input1"); + await input1.setData("1"); + + let input2 = await InputNumber.getInputWithId("input2"); + await input2.setData("5"); + + let input3 = await InputNumber.getInputWithId("input3"); + await input3.setData("10"); + + let input4 = await InputNumber.getInputWithId("input4"); + await input4.setData("50"); + + await Action.clickOnActionWithTitle("Submit"); + + const input1Value: string = await utils.getInputFor("input1"); + Assert.strictEqual(input1Value, "1"); + + const input2Value: string = await utils.getInputFor("input2"); + Assert.strictEqual(input2Value, "5"); + + const input3Value: string = await utils.getInputFor("input3"); + Assert.strictEqual(input3Value, "10"); + + const input4Value: string = await utils.getInputFor("input4"); + Assert.strictEqual(input4Value, "50"); + })); + test("Test interaction with Input.ChoiceSet", (async() => { await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); @@ -121,7 +150,27 @@ describe("Mock function", function() { Assert.strictEqual(dueDateRetrievedValue, "true"); })); - test("Test container select action can be clicked", (async() => { + test("Column: Test select action can be clicked", (async() => { + await utils.goToTestCase("v1.0/Column.SelectAction"); + + let firstColumn = await Column.getContainerWithAction("cool link"); + await firstColumn.click(); + + const firstColumnUrl: string = await utils.getInputFor("url"); + Assert.strictEqual(firstColumnUrl, "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + })); + + test("ColumnSet: Test select action can be clicked", (async() => { + await utils.goToTestCase("v1.0/ColumnSet.SelectAction"); + + let secondColumnSet = await ColumnSet.getContainerWithAction("Remodel your kitchen with our new cabinet styles!"); + await secondColumnSet.click(); + + const secondColumnSetUrl: string = await utils.getInputFor("url"); + Assert.strictEqual(secondColumnSetUrl, "https://www.AdaptiveCards.io"); + })); + + test("Container: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/Container.SelectAction"); let submitContainer = await Container.getContainerWithAction("Submit action"); @@ -135,25 +184,24 @@ describe("Mock function", function() { const emphasisContainerUrl: string = await utils.getInputFor("url"); Assert.strictEqual(emphasisContainerUrl, "https://msn.com"); - })); + })); - test("Test actions are rendered and active below carousel", (async() => { + test("Carousel: Test actions are rendered and active", (async() => { await utils.goToTestCase("v1.6/Carousel.HostConfig"); - let seeMoreAction = await Action.getActionWithTitle("See more"); - await seeMoreAction.click(); + await Action.clickOnActionWithTitle("See more"); const url: string = await utils.getInputFor("url"); Assert.strictEqual(url, "https://adaptivecards.io"); })); - test("Test page limit is honoured", (async() => { + test("Carousel: Test page limit is honoured", (async() => { await utils.goToTestCase("v1.6/Carousel.HostConfig"); await utils.assertElementWithIdDoesNotExist("page10"); })); - test("Unsupported elements are not rendered", (async() => { + test("Carousel: Unsupported elements are not rendered", (async() => { await utils.goToTestCase("v1.6/Carousel.ForbiddenElements"); await utils.assertElementWithIdDoesNotExist("id1"); @@ -165,7 +213,7 @@ describe("Mock function", function() { await utils.assertElementWithIdDoesNotExist("id7"); })); - test("Verify left and right buttons in carousel work", (async() => { + test("Carousel: Verify left and right buttons work", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); @@ -187,7 +235,7 @@ describe("Mock function", function() { Assert.strictEqual(firstPageIsVisible, true); }), timeOutValueForSuddenJumpTest); - test("Unsupported actions are not rendered", (async() => { + test("Carousel: Unsupported actions are not rendered", (async() => { await utils.goToTestCase("v1.6/Carousel.ForbiddenActions"); let showCardAction = await Action.getActionWithTitle("Action.ShowCard"); @@ -198,7 +246,7 @@ describe("Mock function", function() { })); // Giving this test 7 seconds to run - test("Test autoplay is disabled", (async() => { + test("Carousel: Test autoplay is disabled", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); @@ -212,7 +260,7 @@ describe("Mock function", function() { }), 7000); // Giving this test 9 seconds to run - test("Test autoplay is applied", (async() => { + test("Carousel: Test autoplay is applied", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards.Timer"); let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); @@ -228,7 +276,7 @@ describe("Mock function", function() { Assert.strictEqual(secondPageIsVisible, true); }), timeOutValueForCarousel); - test("Test click on navigation does not cause sudden jump", (async() => { + test("Carousel: Test click on navigation does not cause sudden jump", (async() => { await utils.goToTestCase("v1.6/Carousel"); let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); @@ -254,7 +302,7 @@ describe("Mock function", function() { Assert.strictEqual(firstPageIsVisible, false); }), timeOutValueForSuddenJumpTest); - test("Test rtl on carousel", (async() => { + test("Carousel: Test rtl", (async() => { await utils.goToTestCase("v1.6/Carousel.rtl"); for (const page of [["firstCarouselPage", "rtl"], ["secondCarouselPage", "ltr"], ["thirdCarouselPage", "rtl"]]){ From 469d81cd6dafd648c083b4c4637b3e2ebb5e3014 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Mon, 3 Jan 2022 17:02:32 -0800 Subject: [PATCH 05/12] Fix toggle input behaviour --- .../tests/ui-tests/src/card-element-utils.ts | 34 ++++++++++++++----- .../nodejs/tests/ui-tests/src/test-utils.ts | 5 --- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 0dee34398f..a4e14b52c2 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -218,21 +218,37 @@ export class InputToggle extends Input { override async setData(data: string): Promise { if (this.elementWasFound()) { - const isInputSelected: boolean = await this.underlyingElement!.isSelected(); - let mustClick: boolean = true; - if (data === "set") { - mustClick = !isInputSelected; + await this.set(); } else if (data === "unset") { - mustClick = isInputSelected; + await this.unset(); + } else { + await this.toggle(); } + } + } - if (mustClick) - { - this.underlyingElement?.click(); - } + async set(): Promise { + const isInputSelected: boolean = await this.underlyingElement!.isSelected(); + + if (!isInputSelected) { + await this.toggle(); } } + + async unset(): Promise { + const isInputSelected: boolean = await this.underlyingElement!.isSelected(); + + if (isInputSelected) { + await this.toggle(); + } + } + + async toggle(): Promise { + await this.underlyingElement?.click(); + } + + } export class Container extends Element diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts index 9d56b42d04..5985c27c20 100644 --- a/source/nodejs/tests/ui-tests/src/test-utils.ts +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -112,11 +112,6 @@ export class TestUtils { return button; } - async clickOnActionWithTitle(actionTitle: string): Promise { - const button: WebElement = await this.getActionWithTitle(actionTitle); - await button.click(); - } - async waitUntilElementIsCssVisible(id: string, timeoutInMs?: number) { const elementsToWaitFor: WebElement[] = await this.getElementsWithId(id); From 13f6a40194c6f2f25b6660d178769b2ff7ffe54c Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Tue, 4 Jan 2022 13:51:28 -0800 Subject: [PATCH 06/12] Add test for showcard --- samples/v1.0/Elements/Action.ShowCard.json | 5 +- .../src/adaptivecards-ui-testapp.ts | 9 +- .../src/file-retriever-utils.ts | 4 +- .../tests/ui-tests/src/card-element-utils.ts | 156 +++++++++--------- .../nodejs/tests/ui-tests/src/test-utils.ts | 76 +++++---- .../tests/ui-tests/src/ui-tests.test.ts | 31 +++- 6 files changed, 163 insertions(+), 118 deletions(-) diff --git a/samples/v1.0/Elements/Action.ShowCard.json b/samples/v1.0/Elements/Action.ShowCard.json index b22e3f7321..2795e882da 100644 --- a/samples/v1.0/Elements/Action.ShowCard.json +++ b/samples/v1.0/Elements/Action.ShowCard.json @@ -23,7 +23,10 @@ "actions": [ { "type": "Action.Submit", - "title": "Neat!" + "title": "Neat!", + "data": { + "neat": "true" + } } ] } diff --git a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts index 127cd9b301..1c85145f86 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts @@ -15,10 +15,9 @@ function renderTestCasesList() { }); } -function hideInputsDiv() -{ - let testCaseListElement = document.getElementById("retrievedInputsDiv"); - testCaseListElement.style.visibility = "hidden"; +function hideInputsDiv() { + let retrievedInputsDiv = document.getElementById("retrievedInputsDiv"); + retrievedInputsDiv.style.visibility = "hidden"; } window.onload = () => { @@ -55,5 +54,5 @@ window.onload = () => { renderCard(card, cardRenderedCallback); const hideInputsButton = document.getElementById("hideInputsBtn"); - hideInputsButton.click = () => { hideInputsDiv(); }; + hideInputsButton.addEventListener("click", (e: Event) => { hideInputsDiv() }); } diff --git a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts index 07bbee9418..ccee0274c6 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/file-retriever-utils.ts @@ -4,7 +4,9 @@ const v10TestCases: string[] = [ 'Scenarios/ActivityUpdate', 'Elements/Container.SelectAction', 'Elements/Column.SelectAction', - 'Elements/ColumnSet.SelectAction']; + 'Elements/ColumnSet.SelectAction', + 'Elements/Image.SelectAction', + 'Elements/Action.ShowCard']; const v11TestCases: string[] = []; const v12TestCases: string[] = []; const v13TestCases: string[] = [ diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index a4e14b52c2..64921fceb5 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { WebElement, By } from "selenium-webdriver"; +import { WebElement, By, Locator } from "selenium-webdriver"; import { TestUtils } from "./test-utils"; import * as Assert from "assert"; @@ -9,12 +9,36 @@ export class Element { protected constructor() {} - elementWasFound(): boolean - { + elementWasFound(): boolean { return (this.underlyingElement !== undefined); } + + async getCssPropertyValue(propertyName: string): Promise { + if (this.elementWasFound()) { + return await TestUtils.getInstance().getCssPropertyValueForElement(this.underlyingElement!, propertyName); + } else { + return undefined; + } + } + + async elementIsVisible(): Promise { + return (await this.underlyingElement?.getAttribute("visible") === "true") || (await this.underlyingElement!.isDisplayed()); + } + + async elementIsCssVisible(): Promise { + return (await this.getCssPropertyValue("visibility") === "visible"); + } } +export class ActionableElement extends Element { + + async click() + { + await TestUtils.getInstance().clearInputs(); + await this.underlyingElement?.click(); + } +} + export abstract class Input extends Element { protected constructor() { super(); @@ -23,17 +47,7 @@ export abstract class Input extends Element { abstract setData(data: string): Promise; } -export class InputText extends Input { - private constructor(){ - super(); - } - - static async getInputWithId(id: string): Promise - { - let input = new InputText(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-textInput"); - return input; - } +export class TypeableInput extends Input { override async setData(data: string): Promise { @@ -41,10 +55,8 @@ export class InputText extends Input { await this.underlyingElement?.sendKeys(data); } - async isFocused(): Promise - { - if (this.elementWasFound()) - { + async isFocused(): Promise { + if (this.elementWasFound()) { const inputId: string = await this.underlyingElement!.getAttribute("id"); const activeElementId: string = await TestUtils.getInstance().getDriver().switchTo().activeElement().getAttribute("id"); @@ -55,69 +67,63 @@ export class InputText extends Input { } } -export class InputDate extends Input { - private constructor(){ +export class InputText extends TypeableInput { + private constructor() { super(); } - static async getInputWithId(id: string): Promise - { - let input = new InputDate(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput"); + static async getInputWithId(id: string, container?: Container): Promise { + let input = new InputText(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-textInput", container); return input; } +} - override async setData(data: string): Promise - { - await this.underlyingElement?.click(); - await this.underlyingElement?.sendKeys(data); +export class InputDate extends TypeableInput { + private constructor() { + super(); + } + + static async getInputWithId(id: string, container?: Container): Promise { + let input = new InputDate(); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput", container); + return input; } } -export class InputTime extends Input { +export class InputTime extends TypeableInput { private constructor(){ super(); } - static async getInputWithId(id: string): Promise + static async getInputWithId(id: string, container?: Container): Promise { let input = new InputTime(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-timeInput"); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-timeInput", container); return input; - } - - override async setData(data: string): Promise { - await this.underlyingElement?.click(); - await this.underlyingElement?.sendKeys(data); - } - + } } -export class InputNumber extends Input { +export class InputNumber extends TypeableInput { private constructor(){ super(); } - static async getInputWithId(id: string): Promise + static async getInputWithId(id: string, container?: Container): Promise { let input = new InputNumber(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-numberInput"); + input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-numberInput", container); return input; } - - override async setData(data: string): Promise { - await this.underlyingElement?.click(); - await this.underlyingElement?.sendKeys(data); - } } export class InputChoiceSet extends Input { isExpanded: boolean = false; isMultiSelect: boolean = false; - underlyingElements: WebElement[]; + underlyingExpandedElements: WebElement[]; private constructor(isExpanded: boolean, isMultiSelect: boolean) { super(); @@ -126,29 +132,29 @@ export class InputChoiceSet extends Input this.isMultiSelect = isMultiSelect; } - private async getCompactChoiceSet(id: string): Promise + private async getCompactChoiceSet(id: string, container?: Container): Promise { - let compactChoiceSet = await TestUtils.getInstance().getElementWithId(id); - let actualElement = await compactChoiceSet.findElement(By.className("ac-choiceSetInput-compact")); + let compactChoiceSet = await TestUtils.getInstance().getElementWithId(id, container); + let actualElement = await compactChoiceSet?.findElement(By.className("ac-choiceSetInput-compact")); return actualElement; } - private async getExpandedChoiceSet(id: string): Promise + private async getExpandedChoiceSet(id: string, container?: Container): Promise { - return await TestUtils.getInstance().getElementsWithName(id); + return await TestUtils.getInstance().getElementsWithName(id, container); } - static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean): Promise + static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean, container?: Container): Promise { let input = new InputChoiceSet(isExpanded, isMultiSelect); if (!isExpanded) { - input.underlyingElement = await input.getCompactChoiceSet(id); + input.underlyingElement = await input.getCompactChoiceSet(id, container); } else { - input.underlyingElements = await input.getExpandedChoiceSet(id); + input.underlyingExpandedElements = await input.getExpandedChoiceSet(id, container); } return input; } @@ -159,7 +165,7 @@ export class InputChoiceSet extends Input if (this.isExpanded) { if (this.isMultiSelect) { const options: string[] = data.split(","); - for (const choice of this.underlyingElements) + for (const choice of this.underlyingExpandedElements) { const choiceValue = await choice.getAttribute("aria-label"); const choiceIsSelected: boolean = await choice.isSelected(); @@ -174,7 +180,7 @@ export class InputChoiceSet extends Input } else { - for (const choice of this.underlyingElements) + for (const choice of this.underlyingExpandedElements) { const choiceValue = await choice.getAttribute("aria-label"); if (choiceValue === data) { @@ -195,7 +201,7 @@ export class InputChoiceSet extends Input override elementWasFound(): boolean { if (this.isExpanded) { - return this.underlyingElements.length > 0; + return this.underlyingExpandedElements.length > 0; } else { return super.elementWasFound(); @@ -251,8 +257,7 @@ export class InputToggle extends Input { } -export class Container extends Element -{ +export class Container extends ActionableElement { protected constructor() { super(); @@ -263,18 +268,12 @@ export class Container extends Element return new Container(); } - static async getContainerWithAction(tooltip: string): Promise + static async getContainerWithAction(tooltip: string, parentContainer?: Container): Promise { let container = new Container(); - container.underlyingElement = await TestUtils.getInstance().tryGetContainerWithAction(tooltip); + container.underlyingElement = await TestUtils.getInstance().tryGetContainerWithAction(tooltip, parentContainer); return container; } - - async click() - { - await TestUtils.getInstance().clearInputs(); - await this.underlyingElement?.click(); - } } // Currently Column and ColumnSet behave just as container (except columnset having a different class name), @@ -283,7 +282,20 @@ export class Column extends Container {} export class ColumnSet extends Container {} -export class Action extends Element { +export class Image extends ActionableElement { + private constructor(){ + super(); + } + + static async getImage(title: string, container?: Container): Promise + { + let input = new Image(); + input.underlyingElement = await TestUtils.getInstance().getImageWithTitle(title, "ac-image", container); + return input; + } +} + +export class Action extends ActionableElement { private constructor() { super(); @@ -302,11 +314,7 @@ export class Action extends Element { return action; } - async click() - { - await TestUtils.getInstance().clearInputs(); - await this.underlyingElement?.click(); - } + } // As currently there's only one carousel per card is supported then this tests are simpler diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts index 5985c27c20..bd93761ec5 100644 --- a/source/nodejs/tests/ui-tests/src/test-utils.ts +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -3,6 +3,7 @@ import { Builder, By, Capabilities, until, WebDriver, WebElement } from "selenium-webdriver"; import * as Assert from "assert"; import { WaitUntil } from "./wait-utils"; +import { Input, Container, Element } from "./card-element-utils"; export class TestUtils { timeoutForCardRendering: number = 10000; @@ -86,7 +87,13 @@ export class TestUtils { if (this.inputs === undefined) { const retrievedInputsDiv: WebElement = await this.getElementWithId("retrievedInputsDiv"); - await this.driver.wait(until.elementIsVisible(retrievedInputsDiv), this.timeoutForCardRendering); + + let successFunction = async (params: any) => { + let visibility = await TestUtils.getInstance().getCssPropertyValueForElement(retrievedInputsDiv, "visibility"); + return (visibility === "visible"); + }; + + await WaitUntil.customWait(this.timeoutForCardRendering, successFunction, retrievedInputsDiv); const retrievedInputs: string = await retrievedInputsDiv.getText(); @@ -94,22 +101,30 @@ export class TestUtils { } } + getRoot(container?: Container): WebElement | WebDriver { + if (container !== undefined && container.elementWasFound()) { + return container.underlyingElement!; + } else { + return this.getDriver(); + } + } + async tryGetActionWithTitle(actionTitle: string): Promise { const buttonList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-label='${actionTitle}']`)); - return (buttonList.length > 0) ? buttonList[0] : undefined; - } + } - async tryGetContainerWithAction(tooltip: string): Promise { + async tryGetContainerWithAction(tooltip: string, container?: Container): Promise { const containerList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-description='${tooltip}']`)); - return (containerList.length > 0) ? containerList[0] : undefined; } - async getActionWithTitle(actionTitle: string): Promise { - const button: WebElement = await this.driver.findElement(By.xpath(`//*[@title='${actionTitle}']`)); + async getImageWithTitle(title: string, className: string, container?: Container): Promise { + return await this.getRoot(container).findElement(By.xpath(`//*[@title='${title}']`)); + } - return button; + async getActionWithTitle(actionTitle: string): Promise { + return await this.driver.findElement(By.xpath(`//*[@title='${actionTitle}']`)); } async waitUntilElementIsCssVisible(id: string, timeoutInMs?: number) @@ -129,7 +144,13 @@ export class TestUtils { return elementIsVisible; }; - await WaitUntil.customWait(timeOut, successFunction, elementsToWaitFor) + await WaitUntil.customWait(timeOut, successFunction, elementsToWaitFor); + } + + async waitUntilPredicateIsTrue(elements: Element[], successPredicate: Function, timeoutInMs?: number) { + const timeOut = (timeoutInMs !== undefined) ? timeoutInMs : this.timeoutForCardRendering; + + await WaitUntil.customWait(timeOut, successPredicate, elements); } async waitUntilElementIsVisible(id: string) { @@ -142,27 +163,9 @@ export class TestUtils { await this.driver.wait(until.elementIsNotVisible(elementToWaitFor), this.timeoutForCardRendering); } - async getInput(inputId: string, className: string): Promise { - const inputDiv: WebElement = await this.driver.findElement(By.id(inputId)); - const input = await inputDiv.findElement(By.className(className)); - - return input; - } - - async setValueOnInput(inputId: string, className: string, value: string) { - const input: WebElement = await this.getInput(inputId, className); - await input.click(); - await input.sendKeys(value); - } - - async setDateOnDateInput(dateInputId: string, value: string): Promise { - await this.setValueOnInput(dateInputId, "ac-dateInput", value); - } - - async getTextInput(textInputId: string): Promise { - const textInput: WebElement = await this.getInput(textInputId, "ac-textInput"); - - return textInput; + async getInput(inputId: string, className: string, container?: Container): Promise { + const inputDiv: WebElement = await this.getRoot(container).findElement(By.id(inputId)); + return await inputDiv.findElement(By.className(className)); } // Await for x miliseconds @@ -179,22 +182,23 @@ export class TestUtils { return this.driver.findElements(By.id(id)); } - async getElementWithId(id: string): Promise { - return this.driver.findElement(By.id(id)); + async getElementWithId(id: string, container?: Container): Promise { + return await this.getRoot(container).findElement(By.id(id)); } async getCssPropertyValueForElement(element: WebElement, cssProperty: string): Promise { return element.getCssValue(cssProperty); } + /* async getCssPropertyValueForElementWithId(id: string, cssProperty: string): Promise { - const element: WebElement = await this.getElementWithId(id); + const element: WebElement | undefined = await this.getElementWithId(id); return this.getCssPropertyValueForElement(element, cssProperty); } + */ - async getElementsWithName(id: string): Promise - { - return await this.driver.findElements(By.name(id)); + async getElementsWithName(id: string, container?: Container): Promise { + return await this.getRoot(container).findElements(By.name(id)); } } diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index 5b29c9a7d7..1761300e90 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -4,7 +4,7 @@ import * as Assert from "assert"; import { assert } from "console"; import { util } from "prettier"; import { By, WebElement } from "selenium-webdriver"; -import { Action, Carousel, Column, ColumnSet, Container, InputChoiceSet, InputDate, InputNumber, InputText, InputTime, InputToggle } from "./card-element-utils"; +import { Action, Carousel, Column, ColumnSet, Container, InputChoiceSet, InputDate, InputNumber, InputText, InputTime, InputToggle, Image, Element } from "./card-element-utils"; import { TestUtils } from "./test-utils"; describe("Mock function", function() { @@ -150,6 +150,35 @@ describe("Mock function", function() { Assert.strictEqual(dueDateRetrievedValue, "true"); })); + test("Action.ShowCard: Test hidden card is shown", (async() => { + await utils.goToTestCase("v1.0/Action.ShowCard"); + + await Action.clickOnActionWithTitle("Action.ShowCard"); + + const neatAction: Action = await Action.getActionWithTitle("Neat!"); + const neatActionIsVisible = async (params: any) => { + let elements = params as Element[]; + return (await elements[0].elementIsVisible()) || (await elements[0].elementIsCssVisible()); + }; + + await utils.waitUntilPredicateIsTrue([neatAction], neatActionIsVisible); + + await neatAction.click(); + + const neatValue: string = await utils.getInputFor("neat"); + Assert.strictEqual(neatValue, "true"); + })); + + test("Image: Test select action can be clicked", (async() => { + await utils.goToTestCase("v1.0/Image.SelectAction"); + + let image = await Image.getImage("cool link"); + await image.click(); + + const imageUrl: string = await utils.getInputFor("url"); + Assert.strictEqual(imageUrl, "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + })); + test("Column: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/Column.SelectAction"); From 0b72a2e7110af55cc0b504fa1ad601b44a6e2a3b Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 11:21:47 -0800 Subject: [PATCH 07/12] Add more functions --- .../src/rendering-utils.ts | 4 +- .../tests/ui-tests/src/card-element-utils.ts | 340 ++++++++++++------ .../tests/ui-tests/src/selenium-utils.ts | 85 ++++- .../nodejs/tests/ui-tests/src/test-utils.ts | 132 +++---- .../tests/ui-tests/src/ui-tests.test.ts | 118 +++--- .../nodejs/tests/ui-tests/src/wait-utils.ts | 58 ++- 6 files changed, 462 insertions(+), 275 deletions(-) diff --git a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts index 0935de4fe1..4e0768b2ab 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/rendering-utils.ts @@ -89,9 +89,9 @@ export function renderCard(cardJson: any, callbackFunction: Function): void { if (actionType === SubmitAction.JsonTypeName) { - const submitAction: SubmitAction = action as SubmitAction; + const submitAction = action as SubmitAction; - for (let [key, value] of Object.entries(submitAction.data)) + for (const [key, value] of Object.entries(submitAction.data)) { inputsMap[key] = value; } diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 64921fceb5..2cb7cbd805 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -1,13 +1,41 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { WebElement, By, Locator } from "selenium-webdriver"; +import { XpathBuilder } from "./selenium-utils"; import { TestUtils } from "./test-utils"; +import { WaitUtils } from "./wait-utils"; import * as Assert from "assert"; -export class Element { - underlyingElement?: WebElement = undefined; +export abstract class ACElement { + private _id: string; + private _underlyingElement?: WebElement = undefined; + private _container?: ACContainer; - protected constructor() {} + protected constructor(underlyingElement: WebElement | string, container?: ACContainer) { + if (typeof underlyingElement === "string") { + this._id = underlyingElement; + } else { + this._underlyingElement = underlyingElement; + } + + this._container = container; + } + + public get id(): string { + return this._id; + } + + public get underlyingElement(): WebElement | undefined { + return this._underlyingElement; + } + + protected set underlyingElement(underlyingElement: WebElement | undefined) { + this._underlyingElement = underlyingElement; + } + + public get container(): ACContainer | undefined { + return this._container; + } elementWasFound(): boolean { return (this.underlyingElement !== undefined); @@ -28,10 +56,19 @@ export class Element { async elementIsCssVisible(): Promise { return (await this.getCssPropertyValue("visibility") === "visible"); } -} -export class ActionableElement extends Element { + abstract ensureUnderlyingElement(className?: string): Promise; + async getChildrenHtml(): Promise { + return (await this.underlyingElement!.getAttribute("innerHtml")); + } + + async getHtml(): Promise { + return (await this.underlyingElement!.getAttribute("outerHtml")); + } +} + +export abstract class ACActionableElement extends ACElement { async click() { await TestUtils.getInstance().clearInputs(); @@ -39,18 +76,79 @@ export class ActionableElement extends Element { } } -export abstract class Input extends Element { - protected constructor() { - super(); - } +export abstract class Input extends ACElement { + private div: WebElement; + private label?: WebElement; + private errorMessage?: WebElement; abstract setData(data: string): Promise; -} -export class TypeableInput extends Input { + override async ensureUnderlyingElement(className?: string): Promise { + this.div = await TestUtils.getInstance().getInputContainer(this.id, this.container); + this.underlyingElement = await TestUtils.getInstance().getInput(className!, this.div); - override async setData(data: string): Promise - { + await this.getLabel(); + } + + hasLabel(): boolean { + return (this.label !== undefined); + } + + async getLabel(): Promise { + await this.ensureLabel(); + return await this.label?.getText(); + } + + async getErrorMessage(): Promise { + await this.ensureErrorMessage(); + return this.errorMessage?.getText(); + } + + async isRequired(): Promise { + return await this.classListContains(this.underlyingElement!, "ac-input-required"); + } + + async validationFailed(): Promise { + return await this.classListContains(this.underlyingElement!, "ac-input-validation-failed"); + } + + async classListContains(element: WebElement, classToQuery: string): Promise { + return (await element.getAttribute("class")).split(" ").includes(classToQuery); + } + + async ensureLabel(): Promise { + const htmlInputId: string = await this.underlyingElement!.getAttribute("id"); + const labels: WebElement[] = await this.div.findElements(By.xpath(new XpathBuilder().setTagName("label").addAttributeEquals("for", htmlInputId).buildXpath())); + this.label = (labels.length > 0) ? labels[0] : undefined; + } + + async ensureErrorMessage(): Promise { + if (await this.validationFailed()) { + this.errorMessage = await this.div.findElement(By.id(await this.getErrorMessageId())); + } + } + + private async getErrorMessageId(): Promise { + const ariaLabeledBy = await this.underlyingElement!.getAttribute("aria-labelledby"); + + let labels: string[] = ariaLabeledBy.split(" "); + Assert.strictEqual(labels.length, 2, `Input contains more than two classes ${labels}`); + labels.splice(labels.indexOf(await this.label!.getAttribute("id"), 1)); + + return labels[0]; + } + + async getChildrenHtml(): Promise { + return (await this.div.getAttribute("innerHtml")); + } + + async getHtml(): Promise { + return (await this.div.getAttribute("outerHtml")); + } +} + +export class ACTypeableInput extends Input { + override async setData(data: string): Promise { await this.underlyingElement?.click(); await this.underlyingElement?.sendKeys(data); } @@ -58,7 +156,7 @@ export class TypeableInput extends Input { async isFocused(): Promise { if (this.elementWasFound()) { const inputId: string = await this.underlyingElement!.getAttribute("id"); - const activeElementId: string = await TestUtils.getInstance().getDriver().switchTo().activeElement().getAttribute("id"); + const activeElementId: string = await TestUtils.getInstance().driver.switchTo().activeElement().getAttribute("id"); return (inputId === activeElementId); } @@ -67,95 +165,81 @@ export class TypeableInput extends Input { } } -export class InputText extends TypeableInput { - private constructor() { - super(); - } - - static async getInputWithId(id: string, container?: Container): Promise { - let input = new InputText(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-textInput", container); - return input; +export class ACInputText extends ACTypeableInput { + static async getInputWithId(id: string, container?: ACContainer): Promise { + const input = new ACInputText(id, container); + await input.ensureUnderlyingElement("ac-textInput"); + return input; } } -export class InputDate extends TypeableInput { - private constructor() { - super(); +export class ACInputDate extends ACTypeableInput { + static async getInputWithId(id: string, container?: ACContainer): Promise { + const input = new ACInputDate(id, container); + await input.ensureUnderlyingElement("ac-dateInput"); + return input; } - static async getInputWithId(id: string, container?: Container): Promise { - let input = new InputDate(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-dateInput", container); - return input; + setDate(year: number, month: number, day: number) { + this.setData(day.toString().padStart(2, "0") + month.toString().padStart(2, "0") + year.toString()); } } -export class InputTime extends TypeableInput { - - private constructor(){ - super(); - } - - static async getInputWithId(id: string, container?: Container): Promise - { - let input = new InputTime(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-timeInput", container); +export class ACInputTime extends ACTypeableInput { + static async getInputWithId(id: string, container?: ACContainer): Promise { + const input = new ACInputTime(id, container); + await input.ensureUnderlyingElement("ac-timeInput"); return input; - } -} - -export class InputNumber extends TypeableInput { + } - private constructor(){ - super(); + setTime(hour: number, minute: number) { + const meridian: string = (hour >= 12) ? "PM" : "AM"; + this.setData(hour.toString().padStart(2, "0") + minute.toString().padStart(2, "0") + meridian); } +} - static async getInputWithId(id: string, container?: Container): Promise - { - let input = new InputNumber(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-numberInput", container); +export class ACInputNumber extends ACTypeableInput { + static async getInputWithId(id: string, container?: ACContainer): Promise { + const input = new ACInputNumber(id, container); + await input.ensureUnderlyingElement("ac-numberInput"); return input; } } -export class InputChoiceSet extends Input +export class ACInputChoiceSet extends Input { isExpanded: boolean = false; isMultiSelect: boolean = false; underlyingExpandedElements: WebElement[]; - private constructor(isExpanded: boolean, isMultiSelect: boolean) { - super(); + private constructor(id: string, isExpanded: boolean, isMultiSelect: boolean, container?: ACContainer) { + super(id, container); this.isExpanded = isExpanded; this.isMultiSelect = isMultiSelect; } - private async getCompactChoiceSet(id: string, container?: Container): Promise - { - let compactChoiceSet = await TestUtils.getInstance().getElementWithId(id, container); - let actualElement = await compactChoiceSet?.findElement(By.className("ac-choiceSetInput-compact")); - return actualElement; + override async ensureUnderlyingElement(className?: string): Promise { + if (!this.isExpanded) { + await this.getCompactChoiceSet(this.id, this.container); + } + else { + await this.getExpandedChoiceSet(this.id, this.container); + } } - private async getExpandedChoiceSet(id: string, container?: Container): Promise - { - return await TestUtils.getInstance().getElementsWithName(id, container); + private async getCompactChoiceSet(id: string, container?: ACContainer): Promise { + const compactChoiceSet = await TestUtils.getInstance().getElementWithId(id, container); + this.underlyingElement = await compactChoiceSet?.findElement(By.className("ac-choiceSetInput-compact")); } - static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean, container?: Container): Promise - { - let input = new InputChoiceSet(isExpanded, isMultiSelect); + private async getExpandedChoiceSet(id: string, container?: ACContainer): Promise { + this.underlyingExpandedElements = await TestUtils.getInstance().getElementsWithName(id, container); + } - if (!isExpanded) - { - input.underlyingElement = await input.getCompactChoiceSet(id, container); - } - else - { - input.underlyingExpandedElements = await input.getExpandedChoiceSet(id, container); - } + static async getInputWithId(id: string, isExpanded: boolean, isMultiSelect: boolean, container?: ACContainer): Promise { + const input = new ACInputChoiceSet(id, isExpanded, isMultiSelect, container); + await input.ensureUnderlyingElement(); return input; } @@ -209,26 +293,22 @@ export class InputChoiceSet extends Input } } -export class InputToggle extends Input { - private constructor(){ - super(); - } - - static async getInputWithId(id: string): Promise - { - let input = new InputToggle(); - input.underlyingElement = await TestUtils.getInstance().getInput(id, "ac-toggleInput"); +export class ACInputToggle extends Input { + static async getInputWithId(id: string, container?: ACContainer): Promise { + const input = new ACInputToggle(id, container); + await input.ensureUnderlyingElement("ac-toggleInput"); return input; } override async setData(data: string): Promise { - if (this.elementWasFound()) - { + if (this.elementWasFound()) { if (data === "set") { await this.set(); - } else if (data === "unset") { + } + else if (data === "unset") { await this.unset(); - } else { + } + else { await this.toggle(); } } @@ -257,80 +337,102 @@ export class InputToggle extends Input { } -export class Container extends ActionableElement { - protected constructor() - { - super(); +export class ACContainer extends ACActionableElement { + static async getContainer(id: string): Promise { + return new ACContainer(id); } - static async getContainer(id: string): Promise - { - return new Container(); + static async getContainerWithAction(tooltip: string, parentContainer?: ACContainer): Promise { + const container = new ACContainer(tooltip, parentContainer); + await container.ensureUnderlyingElement(); + return container; } - static async getContainerWithAction(tooltip: string, parentContainer?: Container): Promise - { - let container = new Container(); - container.underlyingElement = await TestUtils.getInstance().tryGetContainerWithAction(tooltip, parentContainer); - return container; + override async ensureUnderlyingElement(className?: string): Promise { + this.underlyingElement = await TestUtils.getInstance().tryGetContainerWithAction(this.id, this.container); } } // Currently Column and ColumnSet behave just as container (except columnset having a different class name), // leaving the empty classes for code clarity when used -export class Column extends Container {} +export class ACColumn extends ACContainer {} + +export class ACColumnSet extends ACContainer {} + +export class ACCard extends ACContainer { + + private index: number = 0; -export class ColumnSet extends Container {} + constructor(underlyingElement: string, index?: number) { + super(underlyingElement); -export class Image extends ActionableElement { - private constructor(){ - super(); + if (index !== undefined) { + this.index = index; + } } - static async getImage(title: string, container?: Container): Promise - { - let input = new Image(); - input.underlyingElement = await TestUtils.getInstance().getImageWithTitle(title, "ac-image", container); - return input; + static async getCard(index?: number): Promise { + const card = new ACCard("", index); + await card.ensureUnderlyingElement("ac-adaptiveCard"); + return card; + } + + override async ensureUnderlyingElement(className?: string): Promise { + const cardList = await TestUtils.getInstance().getElementsWithClass(className!); + this.underlyingElement = cardList[this.index]; } } -export class Action extends ActionableElement { - private constructor() +export class ACImage extends ACActionableElement { + static async getImage(title: string, container?: ACContainer): Promise { - super(); + const image = new ACImage(title, container); + await image.ensureUnderlyingElement("ac-image"); + return image; + } + + override async ensureUnderlyingElement(className?: string): Promise { + this.underlyingElement = await TestUtils.getInstance().getImageWithTitle(this.id, className!, this.container); + } + + async getSrc(): Promise { + return await this.underlyingElement!.getAttribute("src"); } +} +export class ACAction extends ACActionableElement { static async clickOnActionWithTitle(title: string): Promise { - let action: Action = await this.getActionWithTitle(title); + let action: ACAction = await this.getActionWithTitle(title); await action.click(); } - static async getActionWithTitle(title: string): Promise + static async getActionWithTitle(title: string): Promise { - let action = new Action(); - action.underlyingElement = await TestUtils.getInstance().tryGetActionWithTitle(title); + let action = new ACAction(title); + await action.ensureUnderlyingElement(); return action; } - + override async ensureUnderlyingElement(className?: string): Promise { + this.underlyingElement = await TestUtils.getInstance().tryGetActionWithTitle(this.id); + } } // As currently there's only one carousel per card is supported then this tests are simpler -export class Carousel { +export class ACCarousel { private constructor(){} static async clickOnLeftArrow() { - const leftArrow = await TestUtils.getInstance().getDriver().findElement(By.className("ac-carousel-left")); + const leftArrow = await TestUtils.getInstance().driver.findElement(By.className("ac-carousel-left")); await leftArrow.click(); } static async clickOnRightArrow() { - const leftArrow = await TestUtils.getInstance().getDriver().findElement(By.className("ac-carousel-right")); + const leftArrow = await TestUtils.getInstance().driver.findElement(By.className("ac-carousel-right")); await leftArrow.click(); } @@ -366,6 +468,6 @@ export class Carousel { // when clicking an arrow while the carousel is moving static async waitForAnimationsToEnd(): Promise { - await TestUtils.getInstance().delay(1000); + await WaitUtils.waitFor(1000); } } \ No newline at end of file diff --git a/source/nodejs/tests/ui-tests/src/selenium-utils.ts b/source/nodejs/tests/ui-tests/src/selenium-utils.ts index 5a7f16910c..3382ce716d 100644 --- a/source/nodejs/tests/ui-tests/src/selenium-utils.ts +++ b/source/nodejs/tests/ui-tests/src/selenium-utils.ts @@ -1,2 +1,85 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. \ No newline at end of file +// Licensed under the MIT License. + +import { By } from "selenium-webdriver"; + +export class ByExtended extends By { + static ariaLabel(ariaLabel: string, tagName?: string): By { + return this.getXpathProperty("aria-label", ariaLabel); + } + + static ariaDescription(ariaDescription: string, tagName?: string): By { + return this.getXpathProperty("aria-description", ariaDescription); + } + + static title(title: string, tagName?: string): By { + return this.getXpathProperty("title", title); + } + + static getXpathProperty(propertyName: string, propertyValue: string, tagName?:string): By { + const tag = tagName? tagName: '*'; + return By.xpath(new XpathBuilder().setTagName(tag).addAttributeEquals(propertyName, propertyValue).buildXpath()); + } +} + +export class XpathBuilder { + private tagName?: string; + private expressions: string[] = []; + + setTagName(tagName: string): XpathBuilder { + this.tagName = tagName; + return this; + } + + addAttributeEquals(attribute: string, value: string): XpathBuilder { + return this.addEquality(value, attribute); + } + + addTextEquals(value: string): XpathBuilder { + return this.addEquality(value); + } + + addAttributeStartsWith(value: string, attribute?:"string"): XpathBuilder { + return this.addFunction("starts-with", value, attribute); + } + + addAttributeContains(value: string, attribute?: string): XpathBuilder { + return this.addFunction("contains", value, attribute); + } + + private addEquality(value: string, attribute?: string): XpathBuilder { + const attributeName = (attribute !== undefined) ? "@" + attribute : "text()"; + this.expressions.push(`${attributeName}='${value}'`); + return this; + } + + private addFunction(functionName: string, value: string, attribute?: string): XpathBuilder { + const attributeName = (attribute !== undefined) ? "@" + attribute : "text()"; + this.expressions.push(`${functionName}(${attributeName},'${value}')`); + return this; + } + + private concatenateExpressions(): string { + let concatenatedExpressions = ""; + let isFirst = true; + + for (const expression of this.expressions) + { + if (!isFirst) { + concatenatedExpressions += " and "; + } + concatenatedExpressions += expression; + + isFirst = false; + } + + return concatenatedExpressions; + } + + buildXpath(): string { + const tagName = (this.tagName === undefined) ? this.tagName : "*"; + const expressions = this.concatenateExpressions(); + return `//${this.tagName}[${expressions}]`; + } + +} \ No newline at end of file diff --git a/source/nodejs/tests/ui-tests/src/test-utils.ts b/source/nodejs/tests/ui-tests/src/test-utils.ts index bd93761ec5..05ba66c59d 100644 --- a/source/nodejs/tests/ui-tests/src/test-utils.ts +++ b/source/nodejs/tests/ui-tests/src/test-utils.ts @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Builder, By, Capabilities, until, WebDriver, WebElement } from "selenium-webdriver"; +import { Builder, By, Capabilities, Locator, until, WebDriver, WebElement } from "selenium-webdriver"; import * as Assert from "assert"; -import { WaitUntil } from "./wait-utils"; -import { Input, Container, Element } from "./card-element-utils"; +import { WaitUntil, WaitUtils } from "./wait-utils"; +import { ACCard, ACContainer } from "./card-element-utils"; +import { ByExtended } from "./selenium-utils"; export class TestUtils { - timeoutForCardRendering: number = 10000; - private driver: WebDriver; + + private _driver: WebDriver; private hideInputsButton: WebElement | undefined = undefined; private inputs: any = undefined; @@ -15,7 +16,7 @@ export class TestUtils { private constructor() { - this.driver = new Builder().withCapabilities(Capabilities.edge()).build(); + this._driver = new Builder().withCapabilities(Capabilities.edge()).build(); } async initializeDriver() @@ -32,28 +33,28 @@ export class TestUtils { static getInstance() { - if (TestUtils.instance == undefined){ + if (TestUtils.instance == undefined) { TestUtils.instance = new TestUtils(); } return TestUtils.instance; } - getDriver() - { - return this.driver; + get driver(): WebDriver { + return this._driver; } - async goToTestCase(testCaseName: string): Promise { + async goToTestCase(testCaseName: string): Promise { const elementLinkText: WebElement = await this.driver.findElement(By.id(testCaseName)); await elementLinkText.click(); const renderedCardContainer: WebElement = await this.getElementWithId("renderedCardSpace"); - await this.driver.wait(until.elementIsVisible(renderedCardContainer), this.timeoutForCardRendering); + await this.driver.wait(until.elementIsVisible(renderedCardContainer), WaitUtils.timeoutForCardRendering); + + return await ACCard.getCard(); } - async getHideInputsButton(): Promise - { + async getHideInputsButton(): Promise { if (this.hideInputsButton === undefined) { this.hideInputsButton = await this.getElementWithId("hideInputsBtn"); @@ -62,30 +63,30 @@ export class TestUtils { return this.hideInputsButton!; } - async clearInputs(): Promise - { + async clearInputs(): Promise { this.inputs = undefined; await this.hideInputsDiv(); } - async hideInputsDiv(): Promise - { + async hideInputsDiv(): Promise { const hideInputsButton = await this.getHideInputsButton(); await hideInputsButton.click(); } async getInputFor(inputId: string): Promise { - if (this.inputs === undefined) - { + if (this.inputs === undefined) { await this.getRetrievedInputs(); } return this.inputs[inputId]; } + async getUrlInRetrievedInputs(): Promise { + return await this.getInputFor("url"); + } + async getRetrievedInputs(): Promise { - if (this.inputs === undefined) - { + if (this.inputs === undefined) { const retrievedInputsDiv: WebElement = await this.getElementWithId("retrievedInputsDiv"); let successFunction = async (params: any) => { @@ -93,7 +94,7 @@ export class TestUtils { return (visibility === "visible"); }; - await WaitUntil.customWait(this.timeoutForCardRendering, successFunction, retrievedInputsDiv); + await WaitUntil.customWait(WaitUtils.timeoutForCardRendering, successFunction, retrievedInputsDiv); const retrievedInputs: string = await retrievedInputsDiv.getText(); @@ -101,88 +102,55 @@ export class TestUtils { } } - getRoot(container?: Container): WebElement | WebDriver { + getRoot(container?: ACContainer): WebElement | WebDriver { if (container !== undefined && container.elementWasFound()) { return container.underlyingElement!; - } else { - return this.getDriver(); + } + else { + return this.driver; } - } + } async tryGetActionWithTitle(actionTitle: string): Promise { - const buttonList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-label='${actionTitle}']`)); + const buttonList: WebElement[] = await this.driver.findElements(ByExtended.ariaLabel(actionTitle)); return (buttonList.length > 0) ? buttonList[0] : undefined; } - async tryGetContainerWithAction(tooltip: string, container?: Container): Promise { - const containerList: WebElement[] = await this.driver.findElements(By.xpath(`//*[@aria-description='${tooltip}']`)); + async tryGetContainerWithAction(tooltip: string, container?: ACContainer): Promise { + const containerList: WebElement[] = await this.getRoot(container).findElements(ByExtended.ariaDescription(tooltip)); return (containerList.length > 0) ? containerList[0] : undefined; } - async getImageWithTitle(title: string, className: string, container?: Container): Promise { - return await this.getRoot(container).findElement(By.xpath(`//*[@title='${title}']`)); - } - - async getActionWithTitle(actionTitle: string): Promise { - return await this.driver.findElement(By.xpath(`//*[@title='${actionTitle}']`)); + async getImageWithTitle(title: string, className: string, container?: ACContainer): Promise { + return await this.getRoot(container).findElement(ByExtended.title(title)); } - async waitUntilElementIsCssVisible(id: string, timeoutInMs?: number) - { - const elementsToWaitFor: WebElement[] = await this.getElementsWithId(id); - const timeOut = timeoutInMs !== undefined ? timeoutInMs : this.timeoutForCardRendering; - - let successFunction = async (params: any) => { - let elementIsVisible: boolean = false; - - for (let element of elementsToWaitFor) - { - let visibility = await TestUtils.getInstance().getCssPropertyValueForElement(element, "visibility"); - elementIsVisible = elementIsVisible || (visibility === "visible"); - } - - return elementIsVisible; - }; - - await WaitUntil.customWait(timeOut, successFunction, elementsToWaitFor); + async getActionWithTitle(actionTitle: string, container?: ACContainer): Promise { + return await this.getRoot(container).findElement(ByExtended.title(actionTitle)); } - async waitUntilPredicateIsTrue(elements: Element[], successPredicate: Function, timeoutInMs?: number) { - const timeOut = (timeoutInMs !== undefined) ? timeoutInMs : this.timeoutForCardRendering; - - await WaitUntil.customWait(timeOut, successPredicate, elements); + async getInputContainer(inputId: string, container?: ACContainer): Promise { + return await this.getRoot(container).findElement(By.id(inputId)); } - async waitUntilElementIsVisible(id: string) { - const elementToWaitFor: WebElement = await this.getElementWithId(id); - await this.driver.wait(until.elementIsVisible(elementToWaitFor), this.timeoutForCardRendering); + async getInput(className: string, container: WebElement): Promise { + return await container.findElement(By.className(className)); } - async waitUntilElementIsNotVisible(id: string) { - const elementToWaitFor: WebElement = await this.getElementWithId(id); - await this.driver.wait(until.elementIsNotVisible(elementToWaitFor), this.timeoutForCardRendering); - } - - async getInput(inputId: string, className: string, container?: Container): Promise { - const inputDiv: WebElement = await this.getRoot(container).findElement(By.id(inputId)); - return await inputDiv.findElement(By.className(className)); - } - - // Await for x miliseconds - async delay(miliseconds: number): Promise { - return new Promise(res => setTimeout(res, miliseconds)); + async getElementsWithClass(className: string, container?: ACContainer): Promise { + return await this.getRoot(container).findElements(By.className(className)); } async assertElementWithIdDoesNotExist(id: string): Promise { - const elementList = await this.driver.findElements(By.id(id)); - Assert.strictEqual(0, elementList.length); + const elementList = await this.getElementsWithId(id); + Assert.strictEqual(elementList.length, 0); } async getElementsWithId(id: string): Promise { return this.driver.findElements(By.id(id)); } - async getElementWithId(id: string, container?: Container): Promise { + async getElementWithId(id: string, container?: ACContainer): Promise { return await this.getRoot(container).findElement(By.id(id)); } @@ -190,15 +158,7 @@ export class TestUtils { return element.getCssValue(cssProperty); } - /* - async getCssPropertyValueForElementWithId(id: string, cssProperty: string): Promise { - const element: WebElement | undefined = await this.getElementWithId(id); - - return this.getCssPropertyValueForElement(element, cssProperty); - } - */ - - async getElementsWithName(id: string, container?: Container): Promise { + async getElementsWithName(id: string, container?: ACContainer): Promise { return await this.getRoot(container).findElements(By.name(id)); } } diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index 1761300e90..a4f1c00d65 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import * as Assert from "assert"; -import { assert } from "console"; -import { util } from "prettier"; -import { By, WebElement } from "selenium-webdriver"; -import { Action, Carousel, Column, ColumnSet, Container, InputChoiceSet, InputDate, InputNumber, InputText, InputTime, InputToggle, Image, Element } from "./card-element-utils"; +import { ACAction, ACCarousel, ACColumn, ACColumnSet, ACContainer, ACInputChoiceSet, ACInputDate, ACInputNumber, ACInputText, ACInputTime, ACInputToggle, ACImage, ACElement } from "./card-element-utils"; import { TestUtils } from "./test-utils"; +import { WaitUtils } from "./wait-utils"; describe("Mock function", function() { let utils: TestUtils; @@ -33,15 +31,15 @@ describe("Mock function", function() { test("Test ActivityUpdate submit", (async() => { await utils.goToTestCase("v1.0/ActivityUpdate"); - await Action.clickOnActionWithTitle("Set due date"); + await ACAction.clickOnActionWithTitle("Set due date"); - let dueDateInput = await InputDate.getInputWithId("dueDate"); + let dueDateInput = await ACInputDate.getInputWithId("dueDate"); await dueDateInput.setData("02041993"); - let commentInput = await InputText.getInputWithId("comment"); + let commentInput = await ACInputText.getInputWithId("comment"); await commentInput.setData("A comment"); - await Action.clickOnActionWithTitle("OK"); + await ACAction.clickOnActionWithTitle("OK"); const dueDateRetrievedValue: string = await utils.getInputFor("dueDate"); Assert.strictEqual(dueDateRetrievedValue, "1993-02-04"); @@ -53,13 +51,13 @@ describe("Mock function", function() { test("Test TextInput get focus on invalid submit", (async() => { await utils.goToTestCase("v1.3/Input.Text.ErrorMessage"); - let commentInput = await InputText.getInputWithId("id1"); + let commentInput = await ACInputText.getInputWithId("id1"); let commentInputIsFocused = await commentInput.isFocused() Assert.strictEqual(commentInputIsFocused, false); - await Action.clickOnActionWithTitle("Submit"); + await ACAction.clickOnActionWithTitle("Submit"); commentInputIsFocused = await commentInput.isFocused() @@ -69,19 +67,19 @@ describe("Mock function", function() { test("Test interaction with Input.Number", (async() => { await utils.goToTestCase("v1.3/Input.Number.ErrorMessage"); - let input1 = await InputNumber.getInputWithId("input1"); + let input1 = await ACInputNumber.getInputWithId("input1"); await input1.setData("1"); - let input2 = await InputNumber.getInputWithId("input2"); + let input2 = await ACInputNumber.getInputWithId("input2"); await input2.setData("5"); - let input3 = await InputNumber.getInputWithId("input3"); + let input3 = await ACInputNumber.getInputWithId("input3"); await input3.setData("10"); - let input4 = await InputNumber.getInputWithId("input4"); + let input4 = await ACInputNumber.getInputWithId("input4"); await input4.setData("50"); - await Action.clickOnActionWithTitle("Submit"); + await ACAction.clickOnActionWithTitle("Submit"); const input1Value: string = await utils.getInputFor("input1"); Assert.strictEqual(input1Value, "1"); @@ -99,16 +97,16 @@ describe("Mock function", function() { test("Test interaction with Input.ChoiceSet", (async() => { await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); - let compactChoiceSet = await InputChoiceSet.getInputWithId("requiredCompactId", false, false); + let compactChoiceSet = await ACInputChoiceSet.getInputWithId("requiredCompactId", false, false); await compactChoiceSet.setData("Option 1"); - let expandedChoiceSet = await InputChoiceSet.getInputWithId("requiredExpandedId", true, false); + let expandedChoiceSet = await ACInputChoiceSet.getInputWithId("requiredExpandedId", true, false); await expandedChoiceSet.setData("Option 2"); - let multiselectChoiceSet = await InputChoiceSet.getInputWithId("requiredMultiselectId", true, true); + let multiselectChoiceSet = await ACInputChoiceSet.getInputWithId("requiredMultiselectId", true, true); await multiselectChoiceSet.setData("Option 1,Option 2"); - await Action.clickOnActionWithTitle("OK"); + await ACAction.clickOnActionWithTitle("OK"); const compactValue: string = await utils.getInputFor("requiredCompactId"); Assert.strictEqual(compactValue, "1"); @@ -123,13 +121,13 @@ describe("Mock function", function() { test("Test interaction with Input.Time", (async() => { await utils.goToTestCase("v1.3/Input.Time.ErrorMessage"); - let dueDateInput = await InputTime.getInputWithId("input1"); + let dueDateInput = await ACInputTime.getInputWithId("input1"); await dueDateInput.setData("0109AM"); - let commentInput = await InputTime.getInputWithId("input4"); + let commentInput = await ACInputTime.getInputWithId("input4"); await commentInput.setData("0230PM"); - await Action.clickOnActionWithTitle("OK"); + await ACAction.clickOnActionWithTitle("OK"); const dueDateRetrievedValue: string = await utils.getInputFor("input1"); Assert.strictEqual(dueDateRetrievedValue, "01:09"); @@ -141,10 +139,10 @@ describe("Mock function", function() { test("Test interaction with Input.Toggle", (async() => { await utils.goToTestCase("v1.3/Input.Toggle.ErrorMessage"); - let toggleInput = await InputToggle.getInputWithId("input2"); + let toggleInput = await ACInputToggle.getInputWithId("input2"); await toggleInput.setData("set"); - await Action.clickOnActionWithTitle("OK"); + await ACAction.clickOnActionWithTitle("OK"); const dueDateRetrievedValue: string = await utils.getInputFor("input2"); Assert.strictEqual(dueDateRetrievedValue, "true"); @@ -153,15 +151,15 @@ describe("Mock function", function() { test("Action.ShowCard: Test hidden card is shown", (async() => { await utils.goToTestCase("v1.0/Action.ShowCard"); - await Action.clickOnActionWithTitle("Action.ShowCard"); + await ACAction.clickOnActionWithTitle("Action.ShowCard"); - const neatAction: Action = await Action.getActionWithTitle("Neat!"); + const neatAction: ACAction = await ACAction.getActionWithTitle("Neat!"); const neatActionIsVisible = async (params: any) => { - let elements = params as Element[]; + let elements = params as ACElement[]; return (await elements[0].elementIsVisible()) || (await elements[0].elementIsCssVisible()); }; - await utils.waitUntilPredicateIsTrue([neatAction], neatActionIsVisible); + await WaitUtils.waitUntilPredicateIsTrue([neatAction], neatActionIsVisible); await neatAction.click(); @@ -172,7 +170,7 @@ describe("Mock function", function() { test("Image: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/Image.SelectAction"); - let image = await Image.getImage("cool link"); + let image = await ACImage.getImage("cool link"); await image.click(); const imageUrl: string = await utils.getInputFor("url"); @@ -182,7 +180,7 @@ describe("Mock function", function() { test("Column: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/Column.SelectAction"); - let firstColumn = await Column.getContainerWithAction("cool link"); + let firstColumn = await ACColumn.getContainerWithAction("cool link"); await firstColumn.click(); const firstColumnUrl: string = await utils.getInputFor("url"); @@ -192,7 +190,7 @@ describe("Mock function", function() { test("ColumnSet: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/ColumnSet.SelectAction"); - let secondColumnSet = await ColumnSet.getContainerWithAction("Remodel your kitchen with our new cabinet styles!"); + let secondColumnSet = await ACColumnSet.getContainerWithAction("Remodel your kitchen with our new cabinet styles!"); await secondColumnSet.click(); const secondColumnSetUrl: string = await utils.getInputFor("url"); @@ -202,13 +200,13 @@ describe("Mock function", function() { test("Container: Test select action can be clicked", (async() => { await utils.goToTestCase("v1.0/Container.SelectAction"); - let submitContainer = await Container.getContainerWithAction("Submit action"); + let submitContainer = await ACContainer.getContainerWithAction("Submit action"); await submitContainer.click(); const submitActionInfo: string = await utils.getInputFor("info"); Assert.strictEqual(submitActionInfo, "My submit action data"); - let emphasisContainer = await Container.getContainerWithAction("Go to a different url"); + let emphasisContainer = await ACContainer.getContainerWithAction("Go to a different url"); await emphasisContainer.click(); const emphasisContainerUrl: string = await utils.getInputFor("url"); @@ -218,7 +216,7 @@ describe("Mock function", function() { test("Carousel: Test actions are rendered and active", (async() => { await utils.goToTestCase("v1.6/Carousel.HostConfig"); - await Action.clickOnActionWithTitle("See more"); + await ACAction.clickOnActionWithTitle("See more"); const url: string = await utils.getInputFor("url"); Assert.strictEqual(url, "https://adaptivecards.io"); @@ -245,32 +243,32 @@ describe("Mock function", function() { test("Carousel: Verify left and right buttons work", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); - let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + let firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); - await Carousel.clickOnRightArrow(); + await ACCarousel.clickOnRightArrow(); - await utils.waitUntilElementIsCssVisible("theSecondCarouselPage", delayForCarouselTimer); + await WaitUtils.waitUntilElementIsCssVisible("theSecondCarouselPage", delayForCarouselTimer); - let secondPageIsVisible = await Carousel.isPageVisible("theSecondCarouselPage"); + let secondPageIsVisible = await ACCarousel.isPageVisible("theSecondCarouselPage"); Assert.strictEqual(secondPageIsVisible, true); - await Carousel.waitForAnimationsToEnd(); - await Carousel.clickOnLeftArrow(); + await ACCarousel.waitForAnimationsToEnd(); + await ACCarousel.clickOnLeftArrow(); - await utils.waitUntilElementIsCssVisible("firstCarouselPage"); + await WaitUtils.waitUntilElementIsCssVisible("firstCarouselPage"); - firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); }), timeOutValueForSuddenJumpTest); test("Carousel: Unsupported actions are not rendered", (async() => { await utils.goToTestCase("v1.6/Carousel.ForbiddenActions"); - let showCardAction = await Action.getActionWithTitle("Action.ShowCard"); + let showCardAction = await ACAction.getActionWithTitle("Action.ShowCard"); Assert.strictEqual(showCardAction.elementWasFound(), false); - let toggleVisibilityAction = await Action.getActionWithTitle("Action.ToggleVisibility"); + let toggleVisibilityAction = await ACAction.getActionWithTitle("Action.ToggleVisibility"); Assert.strictEqual(toggleVisibilityAction.elementWasFound(), false); })); @@ -278,13 +276,13 @@ describe("Mock function", function() { test("Carousel: Test autoplay is disabled", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards"); - let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + let firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); // Await for 5 seconds and verify no change happened - await utils.delay(5000); + await WaitUtils.waitFor(5000); - firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); }), 7000); @@ -292,42 +290,42 @@ describe("Mock function", function() { test("Carousel: Test autoplay is applied", (async() => { await utils.goToTestCase("v1.6/Carousel.ScenarioCards.Timer"); - let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + let firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); // Await for 5 seconds and verify the first page is now hidden - await utils.waitUntilElementIsNotVisible("firstCarouselPage"); + await WaitUtils.waitUntilElementIsNotVisible("firstCarouselPage"); - firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, false); - let secondPageIsVisible = await Carousel.isPageVisible("theSecondCarouselPage"); + let secondPageIsVisible = await ACCarousel.isPageVisible("theSecondCarouselPage"); Assert.strictEqual(secondPageIsVisible, true); }), timeOutValueForCarousel); test("Carousel: Test click on navigation does not cause sudden jump", (async() => { await utils.goToTestCase("v1.6/Carousel"); - let firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + let firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, true); // wait for 2 pages to turn - await utils.waitUntilElementIsCssVisible("last-carousel-page", delayForCarouselTimer * 2); + await WaitUtils.waitUntilElementIsCssVisible("last-carousel-page", delayForCarouselTimer * 2); - firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, false); - let lastPageIsVisible = await Carousel.isPageVisible("last-carousel-page"); + let lastPageIsVisible = await ACCarousel.isPageVisible("last-carousel-page"); Assert.strictEqual(lastPageIsVisible, true); // cause the page to go the 2nd page - await Carousel.waitForAnimationsToEnd(); - await Carousel.clickOnLeftArrow(); + await ACCarousel.waitForAnimationsToEnd(); + await ACCarousel.clickOnLeftArrow(); - await utils.waitUntilElementIsCssVisible("theSecondCarouselPage"); + await WaitUtils.waitUntilElementIsCssVisible("theSecondCarouselPage"); // make sure firstCarouselPage is hidden - firstPageIsVisible = await Carousel.isPageVisible("firstCarouselPage"); + firstPageIsVisible = await ACCarousel.isPageVisible("firstCarouselPage"); Assert.strictEqual(firstPageIsVisible, false); }), timeOutValueForSuddenJumpTest); @@ -335,7 +333,7 @@ describe("Mock function", function() { await utils.goToTestCase("v1.6/Carousel.rtl"); for (const page of [["firstCarouselPage", "rtl"], ["secondCarouselPage", "ltr"], ["thirdCarouselPage", "rtl"]]){ - const pageDirection = await Carousel.getPageDirection(page[0]) + const pageDirection = await ACCarousel.getPageDirection(page[0]) Assert.strictEqual(pageDirection, page[1]); } diff --git a/source/nodejs/tests/ui-tests/src/wait-utils.ts b/source/nodejs/tests/ui-tests/src/wait-utils.ts index 800d84ae84..0ed111a0dc 100644 --- a/source/nodejs/tests/ui-tests/src/wait-utils.ts +++ b/source/nodejs/tests/ui-tests/src/wait-utils.ts @@ -1,24 +1,68 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { until, WebElement } from "selenium-webdriver"; import { TestUtils } from "./test-utils"; +import { ACElement } from "./card-element-utils"; -export class WaitUntil -{ +export class WaitUntil { static async customWait(timeoutMs: number, successFunction: Function, params: any) : Promise { - let milisPassed : number = 0; + let milisPassed: number = 0; - while (milisPassed < timeoutMs) - { - let successFunctionResult = await successFunction(params); + while (milisPassed < timeoutMs) { + const successFunctionResult = await successFunction(params); if (successFunctionResult === true) { break; } milisPassed += 100; - await TestUtils.getInstance().delay(100); + await WaitUtils.waitFor(100); } } +} + +export class WaitUtils { + + static timeoutForCardRendering: number = 10000; + + // Await for x miliseconds + static async waitFor(miliseconds: number): Promise { + return new Promise(res => setTimeout(res, miliseconds)); + } + + static async waitUntilPredicateIsTrue(elements: ACElement[], successPredicate: Function, timeoutInMs?: number) { + const timeOut = (timeoutInMs !== undefined) ? timeoutInMs : WaitUtils.timeoutForCardRendering; + await WaitUntil.customWait(timeOut, successPredicate, elements); + } + + static async waitUntilElementIsVisible(id: string) { + const elementToWaitFor: WebElement = await TestUtils.getInstance().getElementWithId(id); + await TestUtils.getInstance().driver.wait(until.elementIsVisible(elementToWaitFor), WaitUtils.timeoutForCardRendering); + } + + static async waitUntilElementIsNotVisible(id: string) { + const elementToWaitFor: WebElement = await TestUtils.getInstance().getElementWithId(id); + await TestUtils.getInstance().driver.wait(until.elementIsNotVisible(elementToWaitFor), WaitUtils.timeoutForCardRendering); + } + + static async waitUntilElementIsCssVisible(id: string, timeoutInMs?: number) { + const elementsToWaitFor: WebElement[] = await TestUtils.getInstance().getElementsWithId(id); + const timeOut = timeoutInMs !== undefined ? timeoutInMs : WaitUtils.timeoutForCardRendering; + + let successFunction = async (params: any) => { + let elementIsVisible: boolean = false; + + for (let element of elementsToWaitFor) + { + let visibility = await TestUtils.getInstance().getCssPropertyValueForElement(element, "visibility"); + elementIsVisible = elementIsVisible || (visibility === "visible"); + } + + return elementIsVisible; + }; + + await WaitUntil.customWait(timeOut, successFunction, elementsToWaitFor); + } } \ No newline at end of file From 287de9b9a49e13db8fe12a560a174b4db5124c7c Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 15:25:48 -0800 Subject: [PATCH 08/12] Last update --- .../tests/ui-tests/src/card-element-utils.ts | 58 +++++++-- .../tests/ui-tests/src/selenium-utils.ts | 8 +- .../tests/ui-tests/src/ui-tests.test.ts | 123 ++++++++++-------- 3 files changed, 122 insertions(+), 67 deletions(-) diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 2cb7cbd805..5d680557cc 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { WebElement, By, Locator } from "selenium-webdriver"; -import { XpathBuilder } from "./selenium-utils"; +import { ByExtended, XpathBuilder } from "./selenium-utils"; import { TestUtils } from "./test-utils"; import { WaitUtils } from "./wait-utils"; import * as Assert from "assert"; +import { titleIs } from "selenium-webdriver/lib/until"; export abstract class ACElement { private _id: string; @@ -77,9 +78,9 @@ export abstract class ACActionableElement extends ACElement { } export abstract class Input extends ACElement { - private div: WebElement; - private label?: WebElement; - private errorMessage?: WebElement; + protected div: WebElement; + protected label?: WebElement; + protected errorMessage?: WebElement; abstract setData(data: string): Promise; @@ -128,12 +129,14 @@ export abstract class Input extends ACElement { } } - private async getErrorMessageId(): Promise { + protected async getErrorMessageId(): Promise { const ariaLabeledBy = await this.underlyingElement!.getAttribute("aria-labelledby"); let labels: string[] = ariaLabeledBy.split(" "); - Assert.strictEqual(labels.length, 2, `Input contains more than two classes ${labels}`); - labels.splice(labels.indexOf(await this.label!.getAttribute("id"), 1)); + Assert.strictEqual(labels.length, 2, `Labels contains more than two labels ${labels}`); + + labels = labels.splice(labels.indexOf(await this.label!.getAttribute("id")), 1); + Assert.strictEqual(labels.length, 1, `Labels contains more than one label ${labels}`); return labels[0]; } @@ -180,8 +183,8 @@ export class ACInputDate extends ACTypeableInput { return input; } - setDate(year: number, month: number, day: number) { - this.setData(day.toString().padStart(2, "0") + month.toString().padStart(2, "0") + year.toString()); + async setDate(year: number, month: number, day: number) { + await this.setData(month.toString().padStart(2, "0") + day.toString().padStart(2, "0") + year.toString()); } } @@ -192,9 +195,9 @@ export class ACInputTime extends ACTypeableInput { return input; } - setTime(hour: number, minute: number) { + async setTime(hour: number, minute: number) { const meridian: string = (hour >= 12) ? "PM" : "AM"; - this.setData(hour.toString().padStart(2, "0") + minute.toString().padStart(2, "0") + meridian); + await this.setData(hour.toString().padStart(2, "0") + minute.toString().padStart(2, "0") + meridian); } } @@ -220,6 +223,8 @@ export class ACInputChoiceSet extends Input } override async ensureUnderlyingElement(className?: string): Promise { + this.div = await TestUtils.getInstance().getInputContainer(this.id, this.container); + if (!this.isExpanded) { await this.getCompactChoiceSet(this.id, this.container); } @@ -229,11 +234,15 @@ export class ACInputChoiceSet extends Input } private async getCompactChoiceSet(id: string, container?: ACContainer): Promise { - const compactChoiceSet = await TestUtils.getInstance().getElementWithId(id, container); - this.underlyingElement = await compactChoiceSet?.findElement(By.className("ac-choiceSetInput-compact")); + this.underlyingElement = await this.div.findElement(By.className("ac-choiceSetInput-compact")); } private async getExpandedChoiceSet(id: string, container?: ACContainer): Promise { + if (this.isMultiSelect) { + this.underlyingElement = await this.div.findElement(ByExtended.containsClass("ac-choiceSetInput-multiSelect")); + } else { + this.underlyingElement = await this.div.findElement(ByExtended.containsClass("ac-choiceSetInput-expanded")); + } this.underlyingExpandedElements = await TestUtils.getInstance().getElementsWithName(id, container); } @@ -291,6 +300,29 @@ export class ACInputChoiceSet extends Input return super.elementWasFound(); } } + + override async getErrorMessageId(): Promise { + if (this.isExpanded) { + const ariaLabeledBy = await this.underlyingExpandedElements[0].getAttribute("aria-labelledby"); + const choiceId = await this.underlyingExpandedElements[0].getAttribute("id"); + + let labels: string[] = ariaLabeledBy.split(" "); + Assert.strictEqual(labels.length, 3, `Labels contains more than three labels ${labels}`); + + labels.splice(labels.indexOf(await this.label!.getAttribute("id")), 1); + labels.splice(labels.indexOf(await this.getIdOfLabel(choiceId)), 1); + Assert.strictEqual(labels.length, 1, `Labels contains more than one label ${labels}`); + + return labels[0]; + } else { + return super.getErrorMessageId(); + } + } + + private async getIdOfLabel(choiceId: string): Promise { + const label: WebElement = await this.div.findElement(By.xpath(new XpathBuilder().setTagName("label").addAttributeEquals("for", choiceId).buildXpath())); + return await label.getAttribute("id"); + } } export class ACInputToggle extends Input { diff --git a/source/nodejs/tests/ui-tests/src/selenium-utils.ts b/source/nodejs/tests/ui-tests/src/selenium-utils.ts index 3382ce716d..7af2d1bf30 100644 --- a/source/nodejs/tests/ui-tests/src/selenium-utils.ts +++ b/source/nodejs/tests/ui-tests/src/selenium-utils.ts @@ -16,6 +16,10 @@ export class ByExtended extends By { return this.getXpathProperty("title", title); } + static containsClass(className: string, tagName?: string): By { + return By.xpath(new XpathBuilder().addAttributeContains(className, "class").buildXpath()); + } + static getXpathProperty(propertyName: string, propertyValue: string, tagName?:string): By { const tag = tagName? tagName: '*'; return By.xpath(new XpathBuilder().setTagName(tag).addAttributeEquals(propertyName, propertyValue).buildXpath()); @@ -77,9 +81,9 @@ export class XpathBuilder { } buildXpath(): string { - const tagName = (this.tagName === undefined) ? this.tagName : "*"; + const tagName = (this.tagName !== undefined) ? this.tagName : "*"; const expressions = this.concatenateExpressions(); - return `//${this.tagName}[${expressions}]`; + return `//${tagName}[${expressions}]`; } } \ No newline at end of file diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index a4f1c00d65..fd52ce7fae 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -34,7 +34,7 @@ describe("Mock function", function() { await ACAction.clickOnActionWithTitle("Set due date"); let dueDateInput = await ACInputDate.getInputWithId("dueDate"); - await dueDateInput.setData("02041993"); + await dueDateInput.setDate(1993, 2, 4); let commentInput = await ACInputText.getInputWithId("comment"); await commentInput.setData("A comment"); @@ -51,17 +51,17 @@ describe("Mock function", function() { test("Test TextInput get focus on invalid submit", (async() => { await utils.goToTestCase("v1.3/Input.Text.ErrorMessage"); - let commentInput = await ACInputText.getInputWithId("id1"); + const commentInput = await ACInputText.getInputWithId("id1"); - let commentInputIsFocused = await commentInput.isFocused() - - Assert.strictEqual(commentInputIsFocused, false); + Assert.strictEqual(await commentInput.getLabel(), "Required Input.Text *"); + Assert.strictEqual(await commentInput.isRequired(), true); + Assert.strictEqual(await commentInput.getErrorMessage(), undefined); + Assert.strictEqual(await commentInput.isFocused(), false); await ACAction.clickOnActionWithTitle("Submit"); - commentInputIsFocused = await commentInput.isFocused() - - Assert.strictEqual(commentInputIsFocused, true); + Assert.strictEqual(await commentInput.getErrorMessage(), "This is a required input"); + Assert.strictEqual(await commentInput.isFocused(), true); })); test("Test interaction with Input.Number", (async() => { @@ -81,20 +81,13 @@ describe("Mock function", function() { await ACAction.clickOnActionWithTitle("Submit"); - const input1Value: string = await utils.getInputFor("input1"); - Assert.strictEqual(input1Value, "1"); - - const input2Value: string = await utils.getInputFor("input2"); - Assert.strictEqual(input2Value, "5"); - - const input3Value: string = await utils.getInputFor("input3"); - Assert.strictEqual(input3Value, "10"); - - const input4Value: string = await utils.getInputFor("input4"); - Assert.strictEqual(input4Value, "50"); + Assert.strictEqual(await utils.getInputFor("input1"), "1"); + Assert.strictEqual(await utils.getInputFor("input2"), "5"); + Assert.strictEqual(await utils.getInputFor("input3"), "10"); + Assert.strictEqual(await utils.getInputFor("input4"), "50"); })); - test("Test interaction with Input.ChoiceSet", (async() => { + test("Input.ChoiceSet: Test input interaction", (async() => { await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); let compactChoiceSet = await ACInputChoiceSet.getInputWithId("requiredCompactId", false, false); @@ -108,44 +101,77 @@ describe("Mock function", function() { await ACAction.clickOnActionWithTitle("OK"); - const compactValue: string = await utils.getInputFor("requiredCompactId"); - Assert.strictEqual(compactValue, "1"); + Assert.strictEqual(await utils.getInputFor("requiredCompactId"), "1"); + Assert.strictEqual(await utils.getInputFor("requiredExpandedId"), "2"); + Assert.strictEqual(await utils.getInputFor("requiredMultiselectId"), "1,2"); + })); - const expandedValue: string = await utils.getInputFor("requiredExpandedId"); - Assert.strictEqual(expandedValue, "2"); + test("Input.ChoiceSet: Test compact required validation", (async() => { + await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); - const multiselectValue: string = await utils.getInputFor("requiredMultiselectId"); - Assert.strictEqual(multiselectValue, "1,2"); + const compactChoiceSet = await ACInputChoiceSet.getInputWithId("requiredCompactId", false, false); + Assert.strictEqual(await compactChoiceSet.getLabel(), "Required Input.ChoiceSet label (compact) *"); + Assert.strictEqual(await compactChoiceSet.isRequired(), true); + Assert.strictEqual(await compactChoiceSet.getErrorMessage(), undefined); + + await ACAction.clickOnActionWithTitle("OK"); + + Assert.strictEqual(await compactChoiceSet.validationFailed(), true); + Assert.strictEqual(await compactChoiceSet.getErrorMessage(), "This is a required input"); + })); + + test("Input.ChoiceSet: Test expanded required validation", (async() => { + await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); + + const expandedChoiceSet = await ACInputChoiceSet.getInputWithId("requiredExpandedId", true, false); + Assert.strictEqual(await expandedChoiceSet.getLabel(), "Required Input.ChoiceSet label (expanded) *"); + Assert.strictEqual(await expandedChoiceSet.isRequired(), true); + Assert.strictEqual(await expandedChoiceSet.getErrorMessage(), undefined); + + await ACAction.clickOnActionWithTitle("OK"); + + Assert.strictEqual(await expandedChoiceSet.validationFailed(), true); + Assert.strictEqual(await expandedChoiceSet.getErrorMessage(), "This is a required input"); + })); + + test("Input.ChoiceSet: Test multiselect required validation", (async() => { + await utils.goToTestCase("v1.3/Input.ChoiceSet.ErrorMessage"); + + const multiselectChoiceSet = await ACInputChoiceSet.getInputWithId("requiredMultiselectId", true, true); + Assert.strictEqual(await multiselectChoiceSet.getLabel(), "Required Input.ChoiceSet label (expanded, multiselect) *"); + Assert.strictEqual(await multiselectChoiceSet.isRequired(), true); + Assert.strictEqual(await multiselectChoiceSet.getErrorMessage(), undefined); + + await ACAction.clickOnActionWithTitle("OK"); + + Assert.strictEqual(await multiselectChoiceSet.validationFailed(), true); + Assert.strictEqual(await multiselectChoiceSet.getErrorMessage(), "This is a required input"); })); test("Test interaction with Input.Time", (async() => { await utils.goToTestCase("v1.3/Input.Time.ErrorMessage"); - let dueDateInput = await ACInputTime.getInputWithId("input1"); - await dueDateInput.setData("0109AM"); + let timeInput1 = await ACInputTime.getInputWithId("input1"); + await timeInput1.setTime(1, 9); - let commentInput = await ACInputTime.getInputWithId("input4"); - await commentInput.setData("0230PM"); + let timeInput4 = await ACInputTime.getInputWithId("input4"); + await timeInput4.setTime(14, 30); await ACAction.clickOnActionWithTitle("OK"); - const dueDateRetrievedValue: string = await utils.getInputFor("input1"); - Assert.strictEqual(dueDateRetrievedValue, "01:09"); - - const commentRetrievedValue: string = await utils.getInputFor("input4"); - Assert.strictEqual(commentRetrievedValue, "14:30"); + Assert.strictEqual(await utils.getInputFor("input1"), "01:09"); + Assert.strictEqual(await utils.getInputFor("input4"), "14:30"); })); test("Test interaction with Input.Toggle", (async() => { await utils.goToTestCase("v1.3/Input.Toggle.ErrorMessage"); let toggleInput = await ACInputToggle.getInputWithId("input2"); - await toggleInput.setData("set"); + await toggleInput.set(); await ACAction.clickOnActionWithTitle("OK"); - const dueDateRetrievedValue: string = await utils.getInputFor("input2"); - Assert.strictEqual(dueDateRetrievedValue, "true"); + Assert.strictEqual(await utils.getInputFor("input2"), "true"); })); test("Action.ShowCard: Test hidden card is shown", (async() => { @@ -163,8 +189,7 @@ describe("Mock function", function() { await neatAction.click(); - const neatValue: string = await utils.getInputFor("neat"); - Assert.strictEqual(neatValue, "true"); + Assert.strictEqual(await utils.getInputFor("neat"), "true"); })); test("Image: Test select action can be clicked", (async() => { @@ -173,8 +198,7 @@ describe("Mock function", function() { let image = await ACImage.getImage("cool link"); await image.click(); - const imageUrl: string = await utils.getInputFor("url"); - Assert.strictEqual(imageUrl, "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + Assert.strictEqual(await utils.getUrlInRetrievedInputs(), "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); })); test("Column: Test select action can be clicked", (async() => { @@ -183,8 +207,7 @@ describe("Mock function", function() { let firstColumn = await ACColumn.getContainerWithAction("cool link"); await firstColumn.click(); - const firstColumnUrl: string = await utils.getInputFor("url"); - Assert.strictEqual(firstColumnUrl, "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + Assert.strictEqual(await utils.getUrlInRetrievedInputs(), "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); })); test("ColumnSet: Test select action can be clicked", (async() => { @@ -193,8 +216,7 @@ describe("Mock function", function() { let secondColumnSet = await ACColumnSet.getContainerWithAction("Remodel your kitchen with our new cabinet styles!"); await secondColumnSet.click(); - const secondColumnSetUrl: string = await utils.getInputFor("url"); - Assert.strictEqual(secondColumnSetUrl, "https://www.AdaptiveCards.io"); + Assert.strictEqual(await utils.getUrlInRetrievedInputs(), "https://www.AdaptiveCards.io"); })); test("Container: Test select action can be clicked", (async() => { @@ -203,14 +225,12 @@ describe("Mock function", function() { let submitContainer = await ACContainer.getContainerWithAction("Submit action"); await submitContainer.click(); - const submitActionInfo: string = await utils.getInputFor("info"); - Assert.strictEqual(submitActionInfo, "My submit action data"); + Assert.strictEqual(await utils.getInputFor("info"), "My submit action data"); let emphasisContainer = await ACContainer.getContainerWithAction("Go to a different url"); await emphasisContainer.click(); - const emphasisContainerUrl: string = await utils.getInputFor("url"); - Assert.strictEqual(emphasisContainerUrl, "https://msn.com"); + Assert.strictEqual(await utils.getUrlInRetrievedInputs(), "https://msn.com"); })); test("Carousel: Test actions are rendered and active", (async() => { @@ -218,8 +238,7 @@ describe("Mock function", function() { await ACAction.clickOnActionWithTitle("See more"); - const url: string = await utils.getInputFor("url"); - Assert.strictEqual(url, "https://adaptivecards.io"); + Assert.strictEqual(await utils.getUrlInRetrievedInputs(), "https://adaptivecards.io"); })); test("Carousel: Test page limit is honoured", (async() => { From 87c5f62d8b85bd496557492a4883b7e1bc6c9ae8 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 15:31:33 -0800 Subject: [PATCH 09/12] Move setData to input --- .../nodejs/tests/ui-tests/src/card-element-utils.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 5d680557cc..78c56065b6 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -82,7 +82,10 @@ export abstract class Input extends ACElement { protected label?: WebElement; protected errorMessage?: WebElement; - abstract setData(data: string): Promise; + async setData(data: string): Promise { + await this.underlyingElement?.click(); + await this.underlyingElement?.sendKeys(data); + } override async ensureUnderlyingElement(className?: string): Promise { this.div = await TestUtils.getInstance().getInputContainer(this.id, this.container); @@ -151,11 +154,6 @@ export abstract class Input extends ACElement { } export class ACTypeableInput extends Input { - override async setData(data: string): Promise { - await this.underlyingElement?.click(); - await this.underlyingElement?.sendKeys(data); - } - async isFocused(): Promise { if (this.elementWasFound()) { const inputId: string = await this.underlyingElement!.getAttribute("id"); @@ -365,8 +363,6 @@ export class ACInputToggle extends Input { async toggle(): Promise { await this.underlyingElement?.click(); } - - } export class ACContainer extends ACActionableElement { From b5493d686192d086859357526c2ae8b48b02ae81 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 15:35:34 -0800 Subject: [PATCH 10/12] Fix build failure --- source/nodejs/tests/ui-tests/src/card-element-utils.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/nodejs/tests/ui-tests/src/card-element-utils.ts b/source/nodejs/tests/ui-tests/src/card-element-utils.ts index 78c56065b6..67e3a6dd66 100644 --- a/source/nodejs/tests/ui-tests/src/card-element-utils.ts +++ b/source/nodejs/tests/ui-tests/src/card-element-utils.ts @@ -5,7 +5,6 @@ import { ByExtended, XpathBuilder } from "./selenium-utils"; import { TestUtils } from "./test-utils"; import { WaitUtils } from "./wait-utils"; import * as Assert from "assert"; -import { titleIs } from "selenium-webdriver/lib/until"; export abstract class ACElement { private _id: string; @@ -38,6 +37,8 @@ export abstract class ACElement { return this._container; } + abstract ensureUnderlyingElement(className?: string): Promise; + elementWasFound(): boolean { return (this.underlyingElement !== undefined); } @@ -58,8 +59,6 @@ export abstract class ACElement { return (await this.getCssPropertyValue("visibility") === "visible"); } - abstract ensureUnderlyingElement(className?: string): Promise; - async getChildrenHtml(): Promise { return (await this.underlyingElement!.getAttribute("innerHtml")); } @@ -77,7 +76,7 @@ export abstract class ACActionableElement extends ACElement { } } -export abstract class Input extends ACElement { +export class Input extends ACElement { protected div: WebElement; protected label?: WebElement; protected errorMessage?: WebElement; @@ -138,7 +137,7 @@ export abstract class Input extends ACElement { let labels: string[] = ariaLabeledBy.split(" "); Assert.strictEqual(labels.length, 2, `Labels contains more than two labels ${labels}`); - labels = labels.splice(labels.indexOf(await this.label!.getAttribute("id")), 1); + labels.splice(labels.indexOf(await this.label!.getAttribute("id")), 1); Assert.strictEqual(labels.length, 1, `Labels contains more than one label ${labels}`); return labels[0]; From 495b445ef10ec656cc3fd055afe7be937ae3e983 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 15:39:40 -0800 Subject: [PATCH 11/12] Resolve small nit --- .../adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts index 1c85145f86..66ffa09532 100644 --- a/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts +++ b/source/nodejs/adaptivecards-ui-testapp/src/adaptivecards-ui-testapp.ts @@ -16,7 +16,7 @@ function renderTestCasesList() { } function hideInputsDiv() { - let retrievedInputsDiv = document.getElementById("retrievedInputsDiv"); + const retrievedInputsDiv = document.getElementById("retrievedInputsDiv"); retrievedInputsDiv.style.visibility = "hidden"; } From 173f918be8bcf4e346a4684d189a78b6d39f79e7 Mon Sep 17 00:00:00 2001 From: Alberto Medina Gutierrez Date: Fri, 7 Jan 2022 15:45:00 -0800 Subject: [PATCH 12/12] Update a couple more lines --- source/nodejs/tests/ui-tests/src/ui-tests.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts index fd52ce7fae..37e055e795 100644 --- a/source/nodejs/tests/ui-tests/src/ui-tests.test.ts +++ b/source/nodejs/tests/ui-tests/src/ui-tests.test.ts @@ -21,7 +21,7 @@ describe("Mock function", function() { beforeAll(async() => { utils = TestUtils.getInstance(); - utils.initializeDriver(); + await utils.initializeDriver(); }, timeoutForServerStartupInCIBuild); afterAll(async() => { @@ -41,11 +41,8 @@ describe("Mock function", function() { await ACAction.clickOnActionWithTitle("OK"); - const dueDateRetrievedValue: string = await utils.getInputFor("dueDate"); - Assert.strictEqual(dueDateRetrievedValue, "1993-02-04"); - - const commentRetrievedValue: string = await utils.getInputFor("comment"); - Assert.strictEqual(commentRetrievedValue, "A comment"); + Assert.strictEqual(await utils.getInputFor("dueDate"), "1993-02-04"); + Assert.strictEqual(await utils.getInputFor("comment"), "A comment"); })); test("Test TextInput get focus on invalid submit", (async() => {