import { scrollPageToTop, scrollToElement } from "./util.po"; import { browser, $, $$, expect } from '@wdio/globals' import { Key } from 'webdriverio' export class CalculatorPage { getInputLabels() { return $$("ngparam-input input:not([disabled]) label"); } getParamInputs() { return $$("ngparam-input input.form-control"); } async getParamInputsHavingCalcMode() { const ret = []; const inputs = await $$("param-field-line"); for (const inp of inputs) { const toggle = await inp.$("mat-button-toggle.radio_cal > button"); if (await toggle.isExisting()) { ret.push(inp); } } return ret; } getHeader1() { return $("h1"); } /** * return all selects in the calculator which id is in the form "select_*" */ getAllCalculatorSelects() { return $$("mat-select[id^=select_]"); // all mat-select with id starting with "select_" } /** * return select count which id is in the form "select_*" */ async getCalculatorSelectCount() { const sels = await $$("mat-select[id^=select_]"); // all mat-select with id starting with "select_" return sels.length; } /** * get the option count of a select */ async getMatselectOptionCount(select) { const sel = typeof (select) === "string" ? $(`#${select}`) : select; await scrollToElement(sel); if ((await sel.isExisting()) && (await sel.isDisplayed())) { await sel.click(); const options = await $$(".cdk-overlay-container mat-option"); await browser.keys(Key.Escape); // close dropdown return await options.length; } } /** * get the text of the all given select options */ async getMatselectOptionsText(select) { const sel = typeof (select) === "string" ? await $(`#${select}`) : select; await scrollToElement(sel); await browser.pause(200); if ((await sel.isExisting()) && (await sel.isDisplayed())) { await sel.click(); await browser.pause(500); const options = await $$(".cdk-overlay-container mat-option span"); let res = []; const nopt = options.length; for (let o = 0; o < nopt; o++) { const opt = options[o]; res.push(await opt.getText()) } await browser.keys(Key.Escape); // close dropdown await browser.pause(500); return res; } } /** * get select current option */ async getSelectCurrentOption(select) { const id = await select.getAttribute("id"); return $("mat-select#" + id + " div[id^=mat-select-value-]"); } /** * get text of select current option */ async getMatselectCurrentOptionText(select): Promise<string> { const currentOption = await this.getSelectCurrentOption(select); await browser.pause(100); const opt = await currentOption.$("span span"); await browser.pause(100); const res = await opt.getText(); await browser.pause(100); return res; } getSelectById(id: string) { // return $(`#${id}`); return $(`mat-select[id='${id}']`); // IDs cannot start by a number, so use this query form } async isMatSelectPresent(id: string) { const sel = await $("mat-select#" + id); return await sel.isExisting(); } async getSelectValueText(select) { const span = await select.$(".mat-select-value-text > span"); return await span.getText(); } async isSelectEmpty(select) { try { const text = select.$(".mat-select-value-text > span"); await text.getAttribute("outerHTML"); // await anything trigger the error return false; } catch (e) { // should be NoSuchElementError return true; } } getInputById(id: string) { // return $(`#${id}`); return $(`input[id='${id}']`); // IDs cannot start by a number, so use this query form } getNgInputById(id: string) { return $(`ngparam-input input[id='${id}']`); // IDs cannot start by a number, so use this query form } getSaveSessionButton() { return $("dialog-save-session button[type=submit]"); } getCalculateButton() { return this.getButton("trigger-calculate"); } getGeneratePabButton() { return this.getButton("generate-pab"); } getButton(id: string) { return $(`button#${id}`); } getCheckedCalcModeButtons() { return $$(`mat-button-toggle.radio_cal[ng-reflect-checked="true"]`); } getAddStructureButton() { return $("fieldset-container .hyd-window-btns button.add-structure"); } getCopyStructureButton() { return $("fieldset-container .hyd-window-btns button.copy-structure"); } getAllLinkButtons() { return $$("mat-button-toggle.radio_link"); } getPabResultsTable() { return $(".pab-results-table-inner-container table"); } getVariatedResultsTable() { return $(".var-results-inner-container table"); } getAllVariatedResultsTableHeaders() { return $$("fixedvar-results var-results table thead th"); } getAllVariatedResultsRows() { return $$("fixedvar-results var-results table tbody tr"); } getFixedResultsTable() { return $(".fixed-results-inner-container table"); } getAllFixedResultsRows() { return $$("fixed-results table tbody tr"); } /** return nth <tr> of given <table>, starting at 1 */ getNthRow(table, n: number) { return table.$("tbody > tr:nth-of-type(" + n + ")"); } /** return nth <td> of given <tr>, starting at 1 */ getNthColumn(tr, n: number) { return tr.$("td:nth-of-type(" + n + ")"); } async isNgParamPresent(id: string) { const inp = await this.getNgInputById(id); return await inp.isExisting(); } /** * find parameter mode radio button linked to an input */ async getInputRadioButton(input, mode: string) { const tag = await input.getTagName(); await browser.pause(100); const root = tag === "input" ? await this.findParentContainer(input) : input; return await root.$(`mat-button-toggle.radio_${mode}`); } /** * find parameter mode radio button linked to an input */ async getInputRadioButtonFromId(id, mode) { const input = await this.getInputById(id); return await this.getInputRadioButton(input, mode); } async inputHasCalcModeButton(input) { const button = await this.getInputRadioButton(input, "cal"); return await button.isExisting(); } async inputHasLinkModeButton(input) { const button = await this.getInputRadioButton(input, "link"); return await button.isExisting(); } async isRadioButtonChecked(radio) { if (await radio.getTagName() !== "mat-button-toggle") { radio = await this.getParentElement(radio); } const a = await radio.getAttribute("ng-reflect-checked"); return a === "true"; } /** * @returns true if "fixed mode" button linked to an input is selected */ async inputIsInFixedMode(input): Promise<boolean> { const button = await this.getInputRadioButton(input, "fix"); return (await button.getAttribute("ng-reflect-checked")) === "true"; } /** * @returns true if "calculated mode" button linked to an input is selected */ async inputIsInCalculatedMode(input): Promise<boolean> { const button = await this.getInputRadioButton(input, "cal"); return (await button.getAttribute("ng-reflect-checked")) === "true"; } /** * @returns true if "linked mode" button linked to an input is selected */ async inputIsInLinkedMode(input): Promise<boolean> { const button = await this.getInputRadioButton(input, "link"); return (await button.getAttribute("ng-reflect-checked")) === "true"; } async hasResults() { return (await (this.presentAndVisible("fixedvar-results fixed-results > .fixed-results-container")) || (await this.presentAndVisible("fixedvar-results results-chart > chart-results-container")) || (await this.presentAndVisible("section-results fixed-results > .fixed-results-container")) || (await this.presentAndVisible("remous-results #main-chart")) || (await this.presentAndVisible("pab-results pab-results-table")) || (await this.presentAndVisible("pb-results pb-results-table")) || await this.presentAndVisible("pb-results pb-cloison-results") || await this.presentAndVisible("macrorugo-compound-results macrorugo-compound-results-table") || (await this.presentAndVisible("jet-results .fixed-results-container"))); } async presentAndVisible(selector: string) { const elt = await $(selector); const res = (await elt.isExisting()) && (await elt.isDisplayed()); return res; } /** * For a given <table> element, check that values of all cells of all rows in <tbody> are * different from "NaN", "ERR" and optionally "" * @param table a <table> ElementFinder * @param allowEmpty if true, empty "" values will be considered valid */ async allRowsHaveValidResults(table, allowEmpty: boolean = false): Promise<boolean> { let n = 0; let ok = true; const invalidValues = ["ERR", "NaN"]; if (!allowEmpty) { invalidValues.push(""); } const nbCols = await table.$$("thead th").length; const rows = await table.$$("tbody tr"); for (const row of rows) { for (let i = 0; i < nbCols; i++) { // get i_th column of n_th row const val = await row.$("td:nth-of-type(" + (i + 1) + ")").getText(); // console.log(`TABLE VAL ${n}/${i}=>`, val); ok = ok && !invalidValues.includes(val); } n++; } return ok; } /** * Returns true if this.hasResults() is true, and if results table are * not empty and contain only valid values (no "NaN", no "", no "ERR") */ async hasValidResults(): Promise<boolean> { let ok = false; if (await this.hasResults()) { ok = true; // check fixed results const frt = this.getFixedResultsTable(); if ((await frt.isExisting()) && (await frt.isDisplayed())) { ok = ok && (await this.allRowsHaveValidResults(frt)); } // check variated results const vrt = this.getVariatedResultsTable(); if ((await vrt.isExisting()) && (await vrt.isDisplayed())) { ok = ok && (await this.allRowsHaveValidResults(vrt)); } // check PAB results const prt = this.getPabResultsTable(); if ((await prt.isExisting()) && (await prt.isDisplayed())) { ok = ok && (await this.allRowsHaveValidResults(prt, true)); } } return ok; } async hasLog() { return (await this.nbLogEntries()) > 0; } async nbLogEntries() { return await $$("log-entry").length; } /** * return true if the nth log entry is an error */ async nthLogEntryIsError(n: number) { const errs = await $$("log-entry"); const e = errs[n]; const icon = await e.$("div mat-icon"); const style = await icon.getAttribute("style"); return style.indexOf("color: red;") !== -1; } /** * return true if the nth log entry is a warning */ async nthLogEntryIsWarning(n: number) { const errs = await $$("log-entry"); const e = errs[n]; const icon = await e.$("div mat-icon"); const style = await icon.getAttribute("style"); return style.indexOf("color: orange;") !== -1; } async clickSaveCalcButton() { await scrollPageToTop(); return await $("#save-calc").click(); } async clickCloneCalcButton() { const cloneButton = await $("#clone-calc"); await scrollToElement(cloneButton); await cloneButton.click(); } // check that "compute" button is in given enabled/disabled state async checkCalcButtonEnabled(enabled: boolean) { const calcButton = await this.getCalculateButton(); expect(await calcButton.isEnabled()).toBe(enabled); return calcButton; } async getParentElement(elt) { return elt.$(".."); } // find parent element of elt having class "container" async findParentContainer(elt) { let i = 8; // garde fous while (((await elt.getAttribute("class")) !== "container") && (i >= 0)) { elt = await this.getParentElement(elt) i--; } return elt; } async logElement(elt, attr = undefined) { console.log("ELT TAG", await elt.getTagName()); console.log("ELT ID", await elt.getAttribute("id")); console.log("ELT CLASS", await elt.getAttribute("class")); // console.log("ELT VALUE '" + await elt.getAttribute("value") + "'"); console.log("ELT VALUE '" + await elt.getValue() + "'"); console.log("ELT TEXT '" + await elt.getText() + "'"); if (attr !== undefined) { console.log(`ELT ATTR '${attr}'='${await elt.getAttribute(attr)}'`); } } async logParamFieldLine(pfl) { await this.logElement(pfl); const inp = await pfl.$("ngparam-input input.form-control"); await this.logElement(inp) } /** * @param paramFieldLine an <input> element * @param mode "fix", "var", "cal" or "link" */ async setParamMode(elt, mode: string) { await scrollToElement(elt); await browser.pause(100); const button = await this.getInputRadioButton(elt, mode); await button.click(); // for "var" mode, close the modal if (mode === "var") { await browser.pause(500); // wait for the modal to appear //await element(by.css("dialog-edit-param-values .mat-dialog-actions button")).click(); // clique "annuler" et non "valider" : const cancelBtn = await $("dialog-edit-param-values .mat-dialog-actions button.mat-warn"); await cancelBtn.click(); await browser.pause(500); // wait for the navbar to reappear after modal dismissal } else { await browser.pause(200); } } /** * @param elt an <input> element */ async getLinkedValueSelect(elt) { // get parent (div.container) const container = await this.findParentContainer(elt); // find <select> const select = container.$("param-link mat-select"); return select; } /** * Returns an object containing all the calculator's inputs values, indexed * by parameter ID */ async storeAllInputValues() { const inputs = await this.getParamInputs(); const values = {}; for (const i of inputs) { const inputId = await i.getAttribute("id"); const inputValue = await i.getValue(); values[inputId] = +inputValue; // cast to number to avoid false negative (integers starting with 0) }; return values; } /** * Modifies all the calculator's editable inputs values by adding a random digit other than 0 at the end */ async modifyAllInputValues() { const inputs = await this.getParamInputs(); for (const i of inputs) { if (await i.isDisplayed()) { // N in YAXN child of SPP module must not be float const id = await i.getAttribute("id"); const isN = id.includes("_N"); // @TODO strengthen this clodo test // Ob in Grille is set to 0.5 but cannot exceed 0.58; do not touch it const isOb = id === "Ob"; const value = await i.getValue(); const hasDot = value.includes("."); const hasExponent = value.includes("e"); let keys = "" + (Math.floor(Math.random() * 9) + 1); if (!hasDot && !hasExponent && !isN) { keys = "." + keys; } if (!isOb && (await i.getAttribute("disabled")) === null) { await i.addValue(keys); } } }; } /** * check that an input is empty * @param id input id (parameter name) * @param empty true to check input is empty, false to check it is NOT empty */ async checkEmptyInput(id: string, empty: boolean = true) { const inp = await this.getInputById(id); const val = await inp.getValue() if (empty) { expect(val).toEqual(""); } else { expect(val).not.toEqual(""); } } /** * check that a input set is in a given (empty/filled) state * @param inputIds id of the inputs to check * @param emptys empty/not empty state array */ async checkEmptyOrFilledFields(inputIds: string[], emptys: boolean[]) { let n = 0; for (const id of inputIds) { const inp = await this.getInputById(id); const txt = await inp.getValue(); expect(txt === "").toEqual(emptys[n]); n++; } } /** * get help button related to calculator */ getCalculatorHelpButton() { return $("#help-calc"); } /** * reliable input clearing */ async clearInput(inp) { await inp.click(); // make input get focus for browser.keys() to send key sequence to it const txt = await inp.getValue(); const len = txt.length; for (let n = 0; n < len; n++) { await browser.keys(Key.Backspace); } } async closeSnackBar() { const sb = await $("simple-snack-bar button"); if ((await sb.isExisting()) && (await sb.isDisplayed())) { await sb.click(); return true; } return false; } async closeSnackBars(n: number, pause: number) { let stop: boolean; do { stop = !await this.closeSnackBar(); await browser.pause(pause); n--; } while (n > 0 && !stop); } }