首页 > 解决方案 > 针对 Safari 运行量角器测试时出现 WebDriverError

问题描述

首先,非常感谢您提供的所有帮助。我一直在尝试搜索可能的解决方案,但找不到任何线索。

我正在尝试使用 SerenityJS 框架运行一些 UI 测试,该框架是 Protractor 之上的一层。

我的 protractor.config.js 如下所示:

const cwd = process.cwd();
const modules = `${cwd}/node_modules`;
const glob = require(`${modules}/glob`);
const protractor = require.resolve(`${modules}/protractor`);
const protractor_node_modules = protractor.substring(0, protractor.lastIndexOf('node_modules') + 'node_modules'.length);
const seleniumJar = glob.sync(`${cwd}/${protractor_node_modules}/protractor/**/selenium-server-standalone-*.jar`).pop();
const appiumCapabilities = require('./appium-capabilities');

const dashboardTestRootDir = 'dashboard';

const usePhotographer = process.env.PHOTOGRAPHER;

let configObject = {};

configObject = {
    seleniumServerJar: seleniumJar,

    // See https://github.com/angular/protractor/blob/master/docs/timeouts.md
    allScriptsTimeout: 11 * 1000,

    disableChecks: true,

    // See https://github.com/protractor-cucumber-framework/protractor-cucumber-framework#uncaught-exceptions
    ignoreUncaughtExceptions: true,

    framework: 'custom',
    frameworkPath: require.resolve(`${modules}/serenity-js`),

    serenity: {
        stageCueTimeout: 30 * 1000,
    },

    specs: [`${cwd}/features/**/*.feature`],

    cucumberOpts: {
        require: [
            // loads step definitions:
            `${cwd}/features/**/*.ts`, // TypeScript
            `${cwd}/features/**/*.js` // JavaScript
        ],
        format: 'pretty',
        compiler: 'ts:ts-node/register'
    },
};


if (cwd.includes(dashboardTestRootDir)) {

    configObject.multiCapabilities = appiumCapabilities['multiBrowsers'];

    // This is needed to run sequentially in multiCapability, i.e. one browser at a time
    configObject.maxSessions = 1;

    configObject.onPrepare = function() {
        // obtain browser name
        browser.getBrowserName = function() {
            return browser.getCapabilities().then(function(caps) {
                browser.browserName = caps.get('browserName');
                browser.manage().window().maximize();
            }
        )}
        // resolve the promised so the browser name is obtained.
        browser.getBrowserName();
    }
}

exports.config = configObject;

我的浏览器特定配置如下:

// browser: chrome and firefox
const chrome = {
    'browserName': 'chrome',
    'chromeOptions': {
        'args': [
            'disable-infobars'
            // 'incognito',
            // 'disable-extensions',
            // 'show-fps-counter=true'
        ]
    }
};

const firefox = { // https://github.com/mozilla/geckodriver#firefox-capabilities
    'browserName': 'firefox',
    'marionette': true,
    'moz:firefoxOptions': {
        'args': [ // https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
            // '--safe-mode',
            // '--private',
        ]
    }
};

const safari = { // https://developer.apple.com/documentation/webkit/about_webdriver_for_safari
    'browserName': 'safari',
    'safari.options' : {
        technologyPreview: false, // set to true if Safari Technology Preview to be used
        cleanSession: true,
    }
}

// Comment/Uncomment to select the browser used in the test run
const multiBrowsersDirectConnect = [
    chrome,
    // firefox,
]

// Safari 12 and later or Safari Technology Preview is needed to run the tests
const multiBrowsers = [
    // safari need to run alone, as it does not support directConnect
    safari,
]

module.exports = { firefox,
                   safari,
                   multiBrowsers,
                   multiBrowsersDirectConnect, }

我在 Gherkins 中有一些步骤定义如下:

Feature: Login to the dashboard

    As a staff member
    I want to be able to access the dashboard
    So that I can use the dashboard to manage the community

    @sanity @smoke @ruthere
    Scenario: Login to Dashboard with Valid Known Email and Valid Password

        Given a dashboard user named Richard Belding
        When he enters his credentials as DASHBOARD_EMAIL and DASHBOARD_PASSWORD
        Then Richard should see the dashboard welcome page

    @sanity
    Scenario: Login to Dashboard with Valid Unknown Email and Valid Password

        # Valid unknown Email and valid password meaning with valid E-mail & password
        # format, but the user does not exist
        Given a dashboard user named Richard Belding
        When he enters a valid unknown credential as DASHBOARD_EMAIL_UNKNOWN and DASHBOARD_PASSWORD
        Then Richard should be unauthorized to use the dashboard

步骤定义如下所示:

export = function loginSteps() {

    // Setting a large timeout for login, because from time to time, dashboard server
    // response is slow,and it takes a while for the login page to open, especially if
    // tests are run over wifi
    const LOGIN_MAX_TIMEOUT_MILLISECONDS: number = 15 * 1000;
    const LOGIN_MAX_TIMEOUT = { timeout: LOGIN_MAX_TIMEOUT_MILLISECONDS };

    this.Given(/^a dashboard user named (.*)$/, function(name: string) {
        return stage.theActorCalled(name)
                    .attemptsTo(
                        Start.asStaffMember(name),
                    );
    });

    this.When(/^s?he enters (?:his|her) credentials as (.*) and (.*)$/, LOGIN_MAX_TIMEOUT, function(
        emailEnvVariableName: string, passwordEnvVariableName: string) {
            const email = process.env[`${emailEnvVariableName}`];
            const password = process.env[`${passwordEnvVariableName}`];

            return stage.theActorInTheSpotlight()
                        .attemptsTo(
                            Login.withCredentials(email, password),
                            WaitLonger.until(MainMenu.contentOption.TARGET, Is.clickable()),
                            Click.on(MainMenu.contentOption.TARGET),
                            WaitLonger.until(welcomeToCommunityToast.TARGET, Is.absent()),
                        );
    });

    this.Then(/^(.*) should see the dashboard welcome page$/, function(name: string) {
        return expect(stage.theActorInTheSpotlight()
                           .toSee(Dashboard.GetStarted.QUESTION))
                           .eventually
                           .contain(Dashboard.GetStarted.LABEL);
    });

    this.When(/^s?he enters a valid unknown credential as (.*) and (.*)$/, function(
        emailEnvVariableName: string, passwordEnvVariableName: string) {

            const email = process.env[`${emailEnvVariableName}`];
            const password = process.env[`${passwordEnvVariableName}`];

            return stage.theActorInTheSpotlight()
                        .attemptsTo(
                            Login.withCredentials(email, password),
                            WaitLonger.until(unauthorizedToast.TARGET, Is.visible()),
                        );
    });

    this.Then(/^(.*) should be unauthorized to use the dashboard$/, function(name: string) {
        return expect(stage.theActorInTheSpotlight()
                           .toSee(unauthorizedToast.QUESTION))
                           .eventually
                           .include(unauthorizedToast.LABEL);
    });

};

登录功能如下所示:

export class EmailAddress extends TinyTypeOf<string>() {}
export class Password extends TinyTypeOf<string>() {}

export class Credentials extends TinyType {
    static using(email: EmailAddress, password: Password) {
        return new Credentials(email, password);
    }

    private constructor(public readonly email: EmailAddress,
                        public readonly password: Password) {
        super();
    }
}

export class Login implements Task {
    readonly credentials: Credentials;

    static withCredentials(email: string = '', password: string = '') {
        const emailAddress: EmailAddress = new EmailAddress(email);
        const pw: Password = new Password(password);

        return new Login(emailAddress, pw);
    }


    private constructor(email: EmailAddress, password: Password) {
        this.credentials = Credentials.using(email, password);
    }

    @step('{0} logs in to the dashboard')
    // required by the Task interface and delegates the work to lower-level tasks
    performAs(actor: PerformsTasks): PromiseLike<void> {
        const staffEmail: string = this.credentials.email.value;
        const staffPassword: string = this.credentials.password.value;

        return actor.attemptsTo(
            Input.text(staffEmail)
                 .into(TextField.Input.labeled('email').TARGET),
            Input.text(staffPassword)
                 .into(TextField.Input.labeled('password').TARGET),
            // Wait will be adjusted according to different browser
            Wait.for(WaitDuration.BrowserBased.loginDuration()),
            WaitLonger.until(LoginDialog.LoginButton.TARGET, Is.clickable()),
            Click.on(LoginDialog.LoginButton.TARGET),
        );
    }
}

现在,如果我运行这两个测试用例,第一个测试将始终通过,而第二个测试将始终在 step 失败When he enters a valid unknown credential as DASHBOARD_EMAIL_UNKNOWN and DASHBOARD_PASSWORD。并且会抛出异常,堆栈跟踪如下所示:

[protractor-ignore-rest]      WebDriverError:
[protractor-ignore-rest]      Build info: version: '3.14.0', revision: 'aacccce0', time: '2018-08-02T20:13:22.693Z'
[protractor-ignore-rest]      System info: host: 'Steves-MBP.k4connect.private', ip: 'fe80:0:0:0:8e8:8a47:e29:fa6c%en0', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.6', java.version: '1.8.0_172'
[protractor-ignore-rest]      Driver info: driver.version: unknown
[protractor-ignore-rest]          at Object.checkLegacyResponse (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/error.js:546:15)
[protractor-ignore-rest]          at parseHttpResponse (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/http.js:509:13)
[protractor-ignore-rest]          at doSend.then.response (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/http.js:441:30)
[protractor-ignore-rest]          at <anonymous>
[protractor-ignore-rest]          at process._tickCallback (internal/process/next_tick.js:188:7)
[protractor-ignore-rest]      From: Task: WebElement.click()
[protractor-ignore-rest]          at thenableWebDriverProxy.schedule (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/webdriver.js:807:17)
[protractor-ignore-rest]          at WebElement.schedule_ (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/webdriver.js:2010:25)
[protractor-ignore-rest]          at WebElement.click (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/webdriver.js:2092:17)
[protractor-ignore-rest]          at actionFn (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/protractor/built/element.js:89:44)
[protractor-ignore-rest]          at Array.map (<anonymous>)
[protractor-ignore-rest]          at actionResults.getWebElements.then (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/protractor/built/element.js:461:65)
[protractor-ignore-rest]          at ManagedPromise.invokeCallback_ (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/promise.js:1376:14)
[protractor-ignore-rest]          at TaskQueue.execute_ (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/promise.js:3084:14)
[protractor-ignore-rest]          at TaskQueue.executeNext_ (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/promise.js:3067:27)
[protractor-ignore-rest]          at asyncRun (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/selenium-webdriver/lib/promise.js:2927:27)Error
[protractor-ignore-rest]          at ElementArrayFinder.applyAction_ (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/protractor/built/element.js:459:27)
[protractor-ignore-rest]          at ElementArrayFinder.(anonymous function).args [as click] (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/protractor/built/element.js:91:29)
[protractor-ignore-rest]          at ElementFinder.(anonymous function).args [as click] (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/protractor/built/element.js:831:22)
[protractor-ignore-rest]          at Click.performAs (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/serenity-js/src/serenity-protractor/screenplay/interactions/click.ts:13:59)
[protractor-ignore-rest]          at /Users/sdev/k4/github/auto-ui-test/packages/community/node_modules/@serenity-js/core/src/screenplay/actor.ts:112:43
[protractor-ignore-rest]          at <anonymous>
[protractor-ignore-rest]          at process._tickCallback (internal/process/next_tick.js:188:7)
[protractor-ignore-rest]      From: Task: <anonymous>
[protractor-ignore-rest]          at World.stepWrapper (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/serenity-js/src/serenity-cucumber/webdriver_synchroniser.ts:72:18)
[protractor-ignore-rest]          at World.stepWrapper (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/serenity-js/src/serenity-cucumber/webdriver_synchroniser.ts:104:32)
[protractor-ignore-rest]          at World.arity2 (eval at module.exports (/Users/sdev/k4/github/auto-ui-test/packages/dashboard/node_modules/util-arity/arity.js:22:24), <anonymous>:3:45)
[protractor-ignore-rest]          at _combinedTickCallback (internal/process/next_tick.js:131:7)
[protractor-ignore-rest]          at process._tickCallback (internal/process/next_tick.js:180:9)

但是,如果我单独运行它们,它们都会自行通过。

也有人知道我们可以配置 safari 浏览器的 safari.options 是什么,

我试图寻找它们:

https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#safari-specific

如何在 Protractor 配置中为 Safari 启用隐私浏览

但是文档似乎非常有限。

我所有的测试用例都可以在 Google Chrome 和 Firefox 上正常运行。Safari似乎给我带来了很多困难。

我的规格是:操作系统:MacOS High Sierra (10.13.6)

网络驱动程序:3.14.0

Safari 版本:12.0

npm 版本:6.4.0

节点版本:v8.11.3

非虚拟机版本:0.33.11

非常感谢您的所有帮助,如果您需要更多信息,请告诉我。

干杯~

标签: selenium-webdriversafariprotractorcucumber-serenityserenity-js

解决方案


我只是发现实际上,这是由于在单击发生时,DOM 未准备好(某些元素仅在填写某些字段时才变得可单击)的情况下测试进行得太快,因此无法执行操作地方。

一旦填写了用户名和密码,我在点击之前发出了等待,一旦等待后DOM完全加载,我就不再有异常了。


推荐阅读