使用Protractor进行AngularJS测试 - 使用browser.wait()

时间:2017-11-03 11:58:42

标签: javascript angularjs protractor automated-tests

我正在使用Protractor为AngularJS应用程序开发自动化测试套件。

在开发我的测试脚本时,我一直在使用browser.pause(),因此在我执行脚本时,我必须手动告诉它继续测试的每一步。我现在正处于这样的地步,我很高兴我的测试正确执行,并希望删除对browser.pause()的调用,以便我可以让脚本自行完成。< / p>

但我知道,我无法在不添加任何内容的情况下删除对browser.pause()的调用,以允许我的测试暂停/等待浏览器加载,然后再执行下一步(目前,在调用browser.pause()之后,我告诉脚本继续运行所花费的时间足以让浏览器加载下一步测试所需的元素。)

我正在尝试使用browser.wait()来执行此操作,将每个测试的最后一行作为参数传递给browser.wait(),以及超时值(即10秒)。例如:

it('should log the user in to the application', function() {
    browser.wait(loginAsUser(username, password), 10000);
});

该测试原本只是:

browser.pause();

it('should log the user in to the application', function() {
    loginAsUser(username, password);
});

即。在测试用例之外调用browser.pause()会导致浏览器在每个测试的每个步骤之间暂停。

loginAsUser()函数定义为:

function loginAsUser(username, password) {
    usernameInputField.sendKeys(username);
    passwordInputField.sendKeys(password);
    loginBtn.click();
}

当我运行我的测试脚本时,已经将browser.wait()添加到每个测试的最后一个可执行行,就像登录测试一样,我在第一次测试时遇到以下失败(上面的一个登录),其余的测试都失败了,因为它们依赖于那个传递:

  

失败:等待条件必须是类似承诺的对象,函数或条件对象

我不明白为什么我会收到此错误,因为wait条件是一个函数...它被赋予loginAsUser()函数我以上定义了......?谁能向我解释我在这里做错了什么?

修改

所以,似乎问题实际上是我的其余测试(即首先运行login测试,然后在其后依次运行许多其他测试)。

使用我最初的登录测试,测试用例当前正确登录,但下一个要运行的测试失败,给出错误:

  

失败:找不到使用定位器找到的元素:按(链接文字,页面)

这似乎失败了,因为在登录后页面没有时间加载,当我通过调用browser.pause()运行测试时它确实有。

下一个要进行的测试是:

it('should display the Pages menu', function() {
    browser.waitForAngularEnabled(false);
    browser.actions().mouseMove(pagesMenuBtn).perform();
    expect(pageTagBrowserBtn.isDisplayed()).toBeTruthy(); 

    browser.actions().mouseMove(userCircle).perform();
    expect(pageTagBrowserBtn.isDisplayed()).toBeFalsy();
    browser.waitForAngularEnabled(true);
});

pagesMenuBtn被定义为全局变量:

var pagesMenuBtn = element(by.linkText("Pages"));

因此,在运行下一个测试之前,似乎我需要以某种方式为我的应用程序提供登录时加载页面的时间,否则元素将无法找到。

修改

我尝试在&#39;页面中添加对browser.wait()的调用。测试,以便测试等待按钮显示,然后将光标悬停在它上面:

it('should display the Pages menu', function() {
    browser.waitForAngularEnabled(false);
    browser.wait(function() {
        pagesMenuBtn.isDisplayed().then(function(isDisplayed){
        /*  if(!isDisplayed) {
                console.log("Display Pages menu test returning false ");
                return false;
            }
            console.log("Display Pages menu test returning true "); */
            //return true;
            browser.actions().mouseMove(pagesMenuBtn).perform().then(function(isDisplayed){
                expect(pageTagBrowserBtn.isDisplayed()).toBeTruthy();
            });
            browser.actions().mouseMove(userCircle).perform().then(function(isDisplayed){
                expect(pageTagBrowserBtn.isDisplayed()).toBeFalsy();
            });
        });
    }, 5000);
});

但我仍然得到同样的错误:

  

失败:找不到使用定位器找到的元素:按(链接文字,页面)

表示无法找到测试试图悬停的按钮(即,在测试脚本尝试点击的位置,它似乎没有加载到浏览器中它)。

修改

好的,所以我再次更新了我的测试 - 它现在看起来像这样:

it('should display the Pages menu', function() {
    browser.waitForAngularEnabled(false);
    browser.wait(EC.visibilityOf(pagesMenuBtn), 5000).then(
        browser.actions().mouseMove(pagesMenuBtn).perform().then(function(){
            expect(pageTagBrowserBtn.isDisplayed()).toBeTruthy();
        })).then(
        browser.actions().mouseMove(userCircle).perform().then(function(){
            expect(pageTagBrowserBtn.isDisplayed()).toBeFalsy();
        }));
});

我的意图是浏览器会等待显示pagesMenuBtn元素,然后,一旦它出现,光标就会移动到按钮,一旦发生这种情况,它应检查是否显示了pageTagBrowserBtn元素(期望它返回&#39; true&#39;值)。然后光标将移动到页面上的另一个元素(userCircle),然后再次检查是否显示了pageTagBrowserBtn(这次希望它返回&#39; false&#39;值)

然而,当我现在运行我的测试时,它失败了,说明:

  

预期虚假是真的

我不确定为什么会这样......?据我了解,测试应该等待EC.visibilityOf(pagesMenuBtn)的条件在尝试继续测试之前返回true ...所以我不会指望它因为值为false而失败所有 - 如果价值是假的,它应该等到它为真,然后继续 - 至少我的意图是从我所写的。

任何人都可以向我解释这里出了什么问题吗?

2 个答案:

答案 0 :(得分:3)

通常,您永远不必等待硬编码的时间段。

至少等待应与预期条件配对,以便在条件满足后立即解除。

帮助方法示例:

public static async waitForPresenceOf(element: ElementFinder, waitMs?: number): Promise<boolean> {
    return browser.wait(EC.presenceOf(element), waitMs || 5000).then(() => true, () => false);
}

可以在相同的庄园中等待元素的可见性等。

我将发布我当前的帮助方法集合,以防你可以使用其中一个或多个(用TypeScript编写):

import * as webdriver from 'selenium-webdriver';
import By = webdriver.By;


import {browser, element, protractor, by, WebElement, ElementFinder, ElementArrayFinder} from "protractor";

import * as _ from 'lodash';
import {expect} from "./asserts.config";

let EC = protractor.ExpectedConditions;

export default class BrowserHelper {

    public static isAuthenticated: boolean;


    public static async getCurrentUrl(): Promise<string> {
        return browser.getCurrentUrl();
    }

    public static async getDesignId(): Promise<string> {
        let url = await BrowserHelper.getCurrentUrl();

        let designId = '';

        let resources = _.split(url, '/');

        if (_.includes(resources, 'design')) {
            designId = resources[_.indexOf(resources, 'design') + 1];
        }

        return designId;
    }

    public static async scrollTo(x: number | string, y: number | string): Promise<void> {
        await browser.executeScript(`window.scrollTo(${x},${y});`);
    }

    public static async scrollToTop(): Promise<void> {
        await BrowserHelper.scrollTo(0, 0);
    }

    public static async scrollToBottom(): Promise<void> {
        await BrowserHelper.scrollTo(0, 'document.body.scrollHeight');
    }

    public static async scrollToLocator(locator: By | Function): Promise<void> {
        await browser.executeScript('arguments[0].scrollIntoView(false);', element(locator).getWebElement());
    }

    public static async scrollToElement(element: ElementFinder): Promise<void> {
        await browser.executeScript('arguments[0].scrollIntoView(false);', element);
    }

    public static async isElementPresent(element: ElementFinder, waitMs?: number): Promise<boolean> {
        return browser.wait(element.isPresent(), waitMs || 1000).then(() => true, () => false);
    }

    public static async getElement(locator: By | Function, waitMs?: number): Promise<ElementFinder | any> {

        let isPresent = await BrowserHelper.waitForPresenceOf(element(locator), waitMs || 1000);

        return isPresent ? element(locator) : undefined;
    }


    public static getParent(childElement: ElementFinder, levels?: number) {
        let xpath = levels ? '' : '..';

        for (let i = 1; i<=levels; i++) {
            xpath += (i<levels) ? '../' : '..';
        }

        return childElement.element(by.xpath(xpath));
    }

    public static async urlContains(str: string, waitMs?: number): Promise<boolean> {
        return browser.wait(EC.urlContains(str), waitMs || 5000).then(() => true, () => false);
    }

    public static async waitForPresenceOf(element: ElementFinder, waitMs?: number): Promise<boolean> {
        return browser.wait(EC.presenceOf(element), waitMs || 5000).then(() => true, () => false);
    }


    public static async waitForVisibilityOf(element: ElementFinder, waitMs?: number): Promise<boolean> {
        return browser.wait(EC.visibilityOf(element), waitMs || 5000).then(() => true, (e) => false);
    }

    public static async waitForInvisiblityOf(element: ElementFinder, waitMs?: number): Promise<any> {
        await browser.wait(EC.invisibilityOf(element), waitMs || 5000);
    }

    public static async isElementDisplayed(element: ElementFinder): Promise<boolean> {
        return element.isDisplayed().then(() => true, () => false);
    }

    public static async hasElement(locator: By | Function, waitMs?: number): Promise<boolean> {
        return !!BrowserHelper.getElement(locator, waitMs || 5000);
    }

    public static async hasClass(element: ElementFinder, className: string, waitMs?: number): Promise<boolean> {

        await BrowserHelper.isElementPresent(element, waitMs || 5000);

        return new Promise<boolean>((resolve) => {
            element.getAttribute('class').then(function (classes) {
                let hasClass = classes.split(' ').indexOf(className) !== -1;

                resolve(hasClass);
            });
        })
    }


    public static async sendKeys(locator: By | Function, keys: string, clear?: boolean, waitMs?: number): Promise<void> {

        let element: ElementFinder = await BrowserHelper.getElement(locator, waitMs);

        if (clear) {
            await element.clear();
        }

        await element.sendKeys(keys);
    }

    public static async click(locator: By | Function,  waitMs?: number): Promise<void> {
        let element = await BrowserHelper.getElement(locator, waitMs);
        await element.click();
    }

    public static async all(locator: By | Function, waitMs?: number): Promise<ElementFinder[]> {

        // verify presence while allowing it to take a short while for element to appear
        let isPresent: boolean = await BrowserHelper.waitForPresenceOf(element.all(locator).first(), waitMs || 5000);

        if (isPresent) {
            return element.all(locator);
        }

        throw new Error('Could not find Elements matching locator: ' + locator)
    }

    public static async allSub(parentElement: ElementFinder, locator: By | Function): Promise<ElementFinder[]> {
        // assuming element is present (e.g. found using getElement)
        return parentElement.all(locator);
    }

    public static async getElementText(element: ElementFinder): Promise<string> {
        return element.getText().then((text) => text, (e) => "");
    }

    public static async getText(locator: By | Function, waitMs?: number): Promise<string> {
        let element: ElementFinder = await BrowserHelper.getElement(locator, waitMs || 5000);

        return element.getText();
    }

    public static async allText(locator: By | Function, waitMs?: number): Promise<string[]> {
        let textElements: ElementFinder[] = await BrowserHelper.all(locator, waitMs || 5000);

        return BrowserHelper.elementText(...textElements);
    }

    public static async elementText(...elements: ElementFinder[]): Promise<string[]> {
        return Promise.all(_.map(elements, (element: ElementFinder) => {
            return BrowserHelper.getElementText(element);
        }));
    }


    public static async clickElementByLabel(clickablesLocator: By | Function, labelsLocator: By | Function, labelToClick: string,  waitMs?: number): Promise<void> {
        let labels: string[] = await BrowserHelper.allText(labelsLocator);

        // remove leading and trailing whitespaces
        labels = _.map<string, string>(labels, (label) => _.trim(label));


        let elements: ElementFinder[] = await BrowserHelper.all(clickablesLocator);

        expect(labels.length).to.be.eq(elements.length, `clickElementByLabel must have equal amount of clickables and labels`);

        let clickIndex = _.indexOf(labels, labelToClick);

        if (clickIndex >= 0) {
            let elem = elements[clickIndex];
            await BrowserHelper.waitForVisibilityOf(elem, waitMs || 5000);
            await elem.click();
        }
        else {
            throw new Error('Did not find Element with label:' + labelToClick)
        }
    }


    public static async clickElementIfPresent(locator: By | Function): Promise<void> {
        let isClickable = await BrowserHelper.waitForVisibilityOf(element(locator), 10);

        if (isClickable) {
            await element(locator).click();
        }

    }


    public static async getSessionKey(key: string): Promise<any> {
        return browser.driver.executeScript(`return window.sessionStorage.getItem("${key}");`);
    }

    // browser.driver.actions() does currently not properly add typings, so wrapping here for convenience
    public static actions(): webdriver.ActionSequence {
        return browser.driver.actions();
    }

    public static dragTo(to: webdriver.ILocation): webdriver.ActionSequence {

        let actions = BrowserHelper.actions();

        // reduce number of actions sent when testing via external selenium driver
        // if (process.env.E2E_EXTERNAL_SERVER) {
            actions.mouseMove({x: to.x-50, y: to.y});

            // ease in to trigger snap suggestion
            for (let i = 0; i < 10; i++) {
                actions.mouseMove({x: 5, y: 0});
            }
        // }
        // else {
        //
        //     let pxPerStep = 5;
        //
        //     let movedX = 0;
        //     let movedY = 0;
        //
        //     while (Math.abs(movedX) < Math.abs(to.x) || Math.abs(movedY) < Math.abs(to.y)) {
        //         let dx = 0;
        //         let dy = 0;
        //
        //         if (Math.abs(movedX) < Math.abs(to.x)) {
        //             dx = (to.x > 0) ? pxPerStep : -pxPerStep;
        //         }
        //
        //         if (Math.abs(movedY) < Math.abs(to.y)) {
        //             dy = (to.y > 0) ? pxPerStep : -pxPerStep;
        //         }
        //
        //         actions.mouseMove({x: dx, y: dy});
        //
        //         movedX += dx;
        //         movedY += dy;
        //     }
        // }

        return actions;
    }


}

在继续之前,您需要等待测试用例,为此您需要这样做:

it('should log the user in to the application', function(done) {
      BrowserHelper.sendKeys(usernameInputField, username)
        .then(function() { 
            return BrowserHelper.sendKeys(passwordInputField, password) 
        })
        .then(function() {
            return BrowserHelper.click(loginBtn)
        }).then(done)
});

注意done参数。这告诉测试运行器等待调用done回调。

您还应该能够通过简单地返回一个承诺(至少在我的设置中工作)来完成相同的工作

it('should log the user in to the application', function() {
      return BrowserHelper.sendKeys(usernameInputField, username)
        .then(function() { 
            return BrowserHelper.sendKeys(passwordInputField, password) 
        })
        .then(function() {
            return BrowserHelper.click(loginBtn)
        })
});

答案 1 :(得分:1)

您收到错误的原因是,wait方法需要等待条件保留或承诺解决。

例如,假设loginBtn.click();等待id abc的元素显示后,您可以写下面的内容。查看ExpectedConditions了解详情

var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be visible on the dom.
browser.wait(EC.visibilityOf($('#abc')), 5000);

或者您想等待某个自定义条件(例如等待显示此元素),

browser.wait(function(){
    element.isDisplayed().then(function(isDisplayed){
       if(!isDisplayed) {
          return false; //keep looking until its visible;
       }
       return true; //exits right here unless the timeout is already met
    });
},5000);

直接从他们的文档中获取更多信息

  

示例:假设您有一个返回a的函数startTestServer   承诺当服务器准备好请求时。你可以阻止一个   WebDriver客户端承诺:

     

示例代码

var started = startTestServer();
browser.wait(started, 5 * 1000, 'Server should start within 5 seconds');
browser.get(getServerUrl());

根据其他数据进行编辑

pageTagBrowserBtn.isDisplayed()返回一个promise而不是boolean,所以如果你正在使用chai expect,你需要做类似下面的事情

  it('should display the Pages menu', function() {
        browser.waitForAngularEnabled(false);
        browser.wait(EC.visibilityOf(pagesMenuBtn), 5000).then(
            browser.actions().mouseMove(pagesMenuBtn).perform().then(function(){
               pageTagBrowserBtn.isDisplayed().then(function(isDisplayed) {
                      //check isDisplayed is true here
               });
            })).then(
            browser.actions().mouseMove(userCircle).perform().then(function(){
                expect(pageTagBrowserBtn.isDisplayed()).toBeFalsy();
            }));
    });