Skip to content
Snippets Groups Projects
calculator.po.ts 18.46 KiB
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);
    }
}