使用量角器进行 AngularJS 测试 - 使用 browser.wait()

Nob*_*fer 2 javascript automated-tests angularjs protractor

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

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

但是,我知道,如果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);
});
Run Code Online (Sandbox Code Playgroud)

该测试最初只是:

browser.pause();

it('should log the user in to the application', function() {
    loginAsUser(username, password);
});
Run Code Online (Sandbox Code Playgroud)

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

loginAsUser()函数定义为:

function loginAsUser(username, password) {
    usernameInputField.sendKeys(username);
    passwordInputField.sendKeys(password);
    loginBtn.click();
}
Run Code Online (Sandbox Code Playgroud)

当我当前运行我的测试脚本时,browser.wait()与登录测试一样添加到每个测试的最后一个可执行行后,我在第一个测试(上面的日志)中遇到以下失败,其余测试都失败了,因为它们取决于那个通过:

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

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

编辑

因此,似乎问题实际上出在我其余的测试中(即login首先运行测试,然后在它之后依次运行一些其他测试)。

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

失败:使用定位器找不到元素:By(link text, Pages)

这似乎失败了,因为页面在登录后没有时间加载,当我运行测试并调用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);
});
Run Code Online (Sandbox Code Playgroud)

pagesMenuBtn定义为一个全局变量:

var pagesMenuBtn = element(by.linkText("Pages"));
Run Code Online (Sandbox Code Playgroud)

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

编辑

我尝试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);
});
Run Code Online (Sandbox Code Playgroud)

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

失败:使用定位器找不到元素:By(link text, Pages)

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

编辑

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

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();
        }));
});
Run Code Online (Sandbox Code Playgroud)

我的意图是浏览器会等待pagesMenuBtn元素被显示,然后,一旦显示,光标就会移动到按钮上,一旦发生,它应该检查pageTagBrowserBtn元素是否被显示(期望它返回一个“真”值)。然后光标将移动到页面上的另一个元素 ( userCircle),并再次检查 是否pageTagBrowserBtn已显示(这次期望它返回一个“假”值)。

但是,当我现在运行测试时,它失败了,说明:

预期假为真

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

谁能向我解释这里出了什么问题?

cYr*_*ten 5

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

至少等待应该与预期条件配对,以便在满足条件时立即挣脱。

示例助手方法:

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

可以在同一个庄园里等待元素的可见性等。

我将发布我当前的辅助方法集合,以防您可以使用其中的一个或多个(用 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;
    }


}
Run Code Online (Sandbox Code Playgroud)

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

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)
});
Run Code Online (Sandbox Code Playgroud)

注意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)
        })
});
Run Code Online (Sandbox Code Playgroud)