首先,非常感谢您提供的所有帮助。我一直在尝试搜索可能的解决方案,但找不到任何线索。
我正在尝试使用 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
非常感谢您的所有帮助,如果您需要更多信息,请告诉我。
干杯~