mirror of
https://github.com/openstf/stf
synced 2025-10-04 02:09:32 +02:00
Merge branch 'master' into group-feature
This commit is contained in:
commit
1dd9f45f06
26 changed files with 803 additions and 47 deletions
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
8.16.1
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
nodejs 8.16.1
|
|
@ -15,13 +15,14 @@ addons:
|
|||
- yasm
|
||||
env:
|
||||
matrix:
|
||||
- NODE_VERSION=6
|
||||
- NODE_VERSION=8.16.1
|
||||
matrix:
|
||||
allow_failures:
|
||||
- os: osx
|
||||
fast_finish: true
|
||||
before_install:
|
||||
- rm -rf ~/.nvm && git clone --depth 1 https://github.com/creationix/nvm.git ~/.nvm
|
||||
- rm .nvmrc
|
||||
- source ~/.nvm/nvm.sh
|
||||
- nvm install $NODE_VERSION
|
||||
- node --version
|
||||
|
|
|
@ -23,9 +23,9 @@ Thank you to all our sponsors! (please ask your company to also support this ope
|
|||
|
||||
#### Gold Sponsor
|
||||
|
||||
[<img src="doc/sponsors/headspin-wordmark-orange.png?raw=true" alt="HeadSpin" width="400">](https://headspin.io/)
|
||||
[<img src="doc/sponsors/headspin-wordmark-orange.png?raw=true" alt="HeadSpin" width="400">](https://ui.headspin.io/register?referral=start-testing-hs)
|
||||
|
||||
> [HeadSpin](https://headspin.io/) provides secure and scalable STF for iOS integrated with Appium/XCTest/Selenium/Espresso, High speed interaction Audio/Video/Game testing and AI based Root cause analysis for Performance Management. It's free to start using HeadSpin in 150+ locations worldwide! [Try it out for free.](https://ui-dev.headspin.io/signup/23feaa95fec34c49a1da309380807795)
|
||||
> [HeadSpin](https://headspin.io/) provides secure and scalable STF for iOS integrated with Appium/XCTest/Selenium/Espresso, High speed interaction Audio/Video/Game testing and AI based Root cause analysis for Performance Management. It's free to start using HeadSpin in 150+ locations worldwide! [Try it out for free.](https://ui.headspin.io/register?referral=start-testing-hs)
|
||||
|
||||
HeadSpin offers a generous monthly contribution towards STF development.
|
||||
|
||||
|
@ -114,7 +114,7 @@ As the product has evolved from an internal tool running in our internal network
|
|||
|
||||
## Requirements
|
||||
|
||||
* [Node.js](https://nodejs.org/) >= 6.9 (latest stable version preferred)
|
||||
* [Node.js](https://nodejs.org/) 8.x **required** (some dependencies don't support newer versions)
|
||||
* [ADB](http://developer.android.com/tools/help/adb.html) properly set up
|
||||
* [RethinkDB](http://rethinkdb.com/) >= 2.2
|
||||
* [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots)
|
||||
|
|
43
TESTING.md
43
TESTING.md
|
@ -5,20 +5,49 @@
|
|||
|
||||
## E2E Frontend
|
||||
|
||||
### On first run
|
||||
## On first run
|
||||
- `gulp webdriver-update`
|
||||
|
||||
### Chrome Local STF
|
||||
- Connect a device
|
||||
- Run stf
|
||||
- `gulp protractor`
|
||||
|
||||
### Multiple Browsers Local STF with a specific suite
|
||||
|
||||
## Protractor&Jasmine - Local STF tests
|
||||
|
||||
|
||||
---
|
||||
#### Preconditions
|
||||
Test configuration point to Google Chrome browser. Test works on Google Chrome v.77.0.3865.75 together with chromedriver with ver. 77.0.3865.40.
|
||||
|
||||
---
|
||||
|
||||
- Connect a device or start android emulator
|
||||
- Run RethinkDb
|
||||
```
|
||||
rethinkdb
|
||||
```
|
||||
- Run stf
|
||||
```
|
||||
./bin/stf local
|
||||
```
|
||||
Wait till STF will be fully functional and devices will be discovered
|
||||
- Run tests
|
||||
```
|
||||
gulp protractor
|
||||
```
|
||||
|
||||
---
|
||||
#### Info
|
||||
Test results can be found in:
|
||||
test-results/reports-protractor/dashboardReport-protractor/index.html
|
||||
|
||||
---
|
||||
|
||||
===
|
||||
## Multiple Browsers Local STF with a specific suite
|
||||
- Connect a device
|
||||
- Run stf
|
||||
- `gulp protractor --multi --suite devices`
|
||||
|
||||
### Chrome Remote STF
|
||||
## Chrome Remote STF
|
||||
- `export STF_URL='http://stf-url/#!/'`
|
||||
- `export STF_USERNAME='user'`
|
||||
- `export STF_PASSWORD='pass'`
|
||||
|
|
|
@ -1210,6 +1210,7 @@ dbapi.saveDeviceIdentity = function(serial, identity) {
|
|||
, product: identity.product
|
||||
, cpuPlatform: identity.cpuPlatform
|
||||
, openGLESVersion: identity.openGLESVersion
|
||||
, marketName: identity.marketName
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ module.exports = syrup.serial()
|
|||
, identity.product
|
||||
, identity.cpuPlatform
|
||||
, identity.openGLESVersion
|
||||
, identity.marketName
|
||||
))
|
||||
])
|
||||
})
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = syrup.serial()
|
|||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
return adb.shell(options.serial, ['rm', '-f', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports = function(options) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unable to read manifest of "%s"', req.params.id, err.stack)
|
||||
res.status(500)
|
||||
res.status(200)
|
||||
.json({
|
||||
success: false
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ var util = require('util')
|
|||
|
||||
var split = require('split')
|
||||
var Promise = require('bluebird')
|
||||
var androidDeviceList = require('android-device-list')
|
||||
|
||||
var devutil = module.exports = Object.create(null)
|
||||
|
||||
|
@ -135,6 +136,7 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
var product = properties['ro.product.name']
|
||||
var cpuPlatform = properties['ro.board.platform']
|
||||
var openGLESVersion = properties['ro.opengles.version']
|
||||
var marketName = properties['ro.product.device']
|
||||
|
||||
openGLESVersion = parseInt(openGLESVersion, 10)
|
||||
if (isNaN(openGLESVersion)) {
|
||||
|
@ -157,6 +159,13 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
model = model.substr(manufacturer.length)
|
||||
}
|
||||
|
||||
if (marketName) {
|
||||
var devices = androidDeviceList.getDevicesByDeviceId(marketName)
|
||||
if (devices.length > 0) {
|
||||
marketName = devices[0].name
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up remaining model name
|
||||
// model = model.replace(/[_ ]/g, '')
|
||||
return {
|
||||
|
@ -171,5 +180,6 @@ devutil.makeIdentity = function(serial, properties) {
|
|||
, product: product
|
||||
, cpuPlatform: cpuPlatform
|
||||
, openGLESVersion: openGLESVersion
|
||||
, marketName: marketName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ module.exports.readAll = function(stream) {
|
|||
stream.on('readable', readableListener)
|
||||
stream.on('end', endListener)
|
||||
|
||||
readableListener()
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
stream.removeListener('error', errorListener)
|
||||
stream.removeListener('readable', readableListener)
|
||||
|
|
|
@ -408,6 +408,7 @@ message DeviceIdentityMessage {
|
|||
optional string product = 12;
|
||||
optional string cpuPlatform = 13;
|
||||
optional string openGLESVersion = 14;
|
||||
optional string marketName = 15;
|
||||
}
|
||||
|
||||
message DeviceProperty {
|
||||
|
|
16
package.json
16
package.json
|
@ -37,6 +37,7 @@
|
|||
"adbkit": "^2.11.1",
|
||||
"adbkit-apkreader": "^3.1.1",
|
||||
"adbkit-monkey": "^1.0.1",
|
||||
"android-device-list": "^1.2.1",
|
||||
"aws-sdk": "^2.4.13",
|
||||
"basic-auth": "^1.0.3",
|
||||
"bluebird": "^2.10.1",
|
||||
|
@ -99,7 +100,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"async": "^2.0.1",
|
||||
"bower": "^1.7.2",
|
||||
"bower": "^1.8.8",
|
||||
"chai": "^3.4.1",
|
||||
"css-loader": "^0.23.1",
|
||||
"del": "^2.0.1",
|
||||
|
@ -108,6 +109,7 @@
|
|||
"exports-loader": "^0.6.2",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-angular-gettext": "^2.1.0",
|
||||
"gulp-eslint": "^3.0.1",
|
||||
|
@ -120,13 +122,13 @@
|
|||
"http-https": "^1.0.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"jasmine-reporters": "^2.1.1",
|
||||
"jasmine-reporters": "^2.3.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^1.1.2",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma": "^1.7.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
"karma-junit-reporter": "^1.1.0",
|
||||
"karma-opera-launcher": "^1.0.0",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
|
@ -138,8 +140,8 @@
|
|||
"node-libs-browser": "^1.0.0",
|
||||
"node-sass": "^3.4.2",
|
||||
"phantomjs-prebuilt": "^2.1.11",
|
||||
"protractor": "^4.0.3",
|
||||
"protractor-html-screenshot-reporter": "0.0.21",
|
||||
"protractor": "^5.4.1",
|
||||
"protractor-html-reporter-2": "1.0.4",
|
||||
"raw-loader": "^0.5.1",
|
||||
"sass-loader": "^4.0.0",
|
||||
"script-loader": "^0.7.0",
|
||||
|
|
|
@ -229,6 +229,12 @@ module.exports = function DeviceColumnService($filter, gettext, SettingsService,
|
|||
return device.manufacturer || ''
|
||||
}
|
||||
})
|
||||
, marketName: TextCell({
|
||||
title: gettext('Market name')
|
||||
, value: function(device) {
|
||||
return device.marketName || ''
|
||||
}
|
||||
})
|
||||
, sdk: NumberCell({
|
||||
title: gettext('SDK')
|
||||
, defaultOrder: 'desc'
|
||||
|
|
|
@ -59,6 +59,10 @@ module.exports = function DeviceListCtrl(
|
|||
name: 'manufacturer'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'marketName'
|
||||
, selected: false
|
||||
}
|
||||
, {
|
||||
name: 'sdk'
|
||||
, selected: false
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
describe('Control Page', function() {
|
||||
var DeviceListPage = require('../devices')
|
||||
var deviceListPage = new DeviceListPage()
|
||||
var localhost = browser.baseUrl
|
||||
|
||||
var ControlPage = function() {
|
||||
this.get = function() {
|
||||
browser.get(protractor.getInstance().baseUrl + 'control')
|
||||
browser.get(localhost + 'control')
|
||||
}
|
||||
this.kickDeviceButton = element.all(by.css('.kick-device')).first()
|
||||
this.kickDevice = function() {
|
||||
|
@ -26,8 +27,8 @@ describe('Control Page', function() {
|
|||
|
||||
browser.sleep(500)
|
||||
|
||||
browser.getLocationAbsUrl().then(function(newUrl) {
|
||||
expect(newUrl).toMatch(protractor.getInstance().baseUrl + 'control')
|
||||
browser.getCurrentUrl().then(function(newUrl) {
|
||||
expect(newUrl).toContain(localhost + 'control/')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -135,10 +136,10 @@ describe('Control Page', function() {
|
|||
it('should stop controlling an usable device', function() {
|
||||
controlPage.kickDevice()
|
||||
|
||||
waitUrl(/devices/)
|
||||
browser.wait(waitUrl(/devices/), 5000)
|
||||
|
||||
browser.getLocationAbsUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(protractor.getInstance().baseUrl + 'devices')
|
||||
browser.getCurrentUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(localhost + 'devices')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ describe('Device Page', function() {
|
|||
|
||||
it('should go to Devices List page', function() {
|
||||
deviceListPage.get()
|
||||
browser.getLocationAbsUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(protractor.getInstance().baseUrl + 'devices')
|
||||
browser.getCurrentUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(browser.baseUrl + 'devices')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = function DeviceListPage() {
|
||||
this.get = function() {
|
||||
// TODO: Let's get rid off the login first
|
||||
browser.get(protractor.getInstance().baseUrl + 'devices')
|
||||
browser.get(browser.baseUrl + 'devices')
|
||||
}
|
||||
this.devices = element(by.model('tracker.devices'))
|
||||
this.devicesByCss = element.all(by.css('ul.devices-icon-view > li'))
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = function BrowserLogs(opts) {
|
|||
}
|
||||
|
||||
browser.getCapabilities().then(function(cap) {
|
||||
var browserName = ' ' + cap.caps_.browserName + ' log '
|
||||
var browserName = ' ' + cap.browserName + ' log '
|
||||
var browserStyled = chalk.bgBlue.white.bold(browserName) + ' '
|
||||
|
||||
browser.manage().logs().get('browser').then(function(browserLogs) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module.exports = function LoginPage() {
|
||||
this.login = protractor.getInstance().params.login
|
||||
this.login = browser.params.login
|
||||
|
||||
this.get = function() {
|
||||
return browser.get(this.login.url)
|
||||
|
@ -17,17 +17,24 @@ module.exports = function LoginPage() {
|
|||
this.setName = function(username) {
|
||||
return this.username.sendKeys(username)
|
||||
}
|
||||
|
||||
this.setEmail = function(email) {
|
||||
return this.email.sendKeys(email)
|
||||
}
|
||||
|
||||
this.setPassword = function(password) {
|
||||
return this.password.sendKeys(password)
|
||||
}
|
||||
|
||||
this.submit = function() {
|
||||
return this.username.submit()
|
||||
}
|
||||
|
||||
this.doLogin = function() {
|
||||
var EC = protractor.ExpectedConditions
|
||||
var timeout = 15000
|
||||
this.get()
|
||||
browser.wait(EC.presenceOf(element(by.css('[value="Log In"]'))), timeout)
|
||||
this.setName(this.login.username)
|
||||
if (this.login.method === 'ldap') {
|
||||
this.setPassword(this.login.password)
|
||||
|
@ -43,6 +50,7 @@ module.exports = function LoginPage() {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.cleanUp = function() {
|
||||
this.username = null
|
||||
this.password = null
|
||||
|
|
|
@ -2,14 +2,20 @@ describe('Login Page', function() {
|
|||
var LoginPage = require('./')
|
||||
var loginPage = new LoginPage()
|
||||
|
||||
beforeEach(function() {
|
||||
browser.executeScript('window.localStorage.clear();')
|
||||
browser.executeScript('window.sessionStorage.clear();')
|
||||
browser.driver.manage().deleteAllCookies()
|
||||
})
|
||||
|
||||
it('should have an url to login', function() {
|
||||
expect(loginPage.login.url).toMatch('http')
|
||||
})
|
||||
|
||||
it('should login with method: "' + loginPage.login.method + '"', function() {
|
||||
loginPage.doLogin().then(function() {
|
||||
browser.getLocationAbsUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(protractor.getInstance().baseUrl + 'devices')
|
||||
browser.getCurrentUrl().then(function(newUrl) {
|
||||
expect(newUrl).toBe(browser.baseUrl + 'devices')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var config = require('./protractor.conf').config
|
||||
//var LoginPage = require('./e2e/login')
|
||||
//var HtmlReporter = require('protractor-html-screenshot-reporter')
|
||||
//var WaitUrl = require('./e2e/helpers/wait-url')
|
||||
|
||||
config.chromeOnly = false
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
var LoginPage = require('./e2e/login')
|
||||
var BrowserLogs = require('./e2e/helpers/browser-logs')
|
||||
//var FailFast = require('./e2e/helpers/fail-fast')
|
||||
var HtmlReporter = require('protractor-html-screenshot-reporter')
|
||||
var jasmineReporters = require('jasmine-reporters')
|
||||
var WaitUrl = require('./e2e/helpers/wait-url')
|
||||
var HTMLReport = require('protractor-html-reporter-2')
|
||||
|
||||
var reportsDirectory = './test-results/reports-protractor'
|
||||
var dashboardReportDirectory = reportsDirectory + '/dashboardReport'
|
||||
|
||||
module.exports.config = {
|
||||
baseUrl: process.env.STF_URL || 'http://localhost:7100/#!/',
|
||||
|
@ -17,7 +21,7 @@ module.exports.config = {
|
|||
params: {
|
||||
login: {
|
||||
url: process.env.STF_LOGINURL || process.env.STF_URL ||
|
||||
'http://localhost:7120',
|
||||
'http://localhost:7100',
|
||||
username: process.env.STF_USERNAME || 'test_user',
|
||||
email: process.env.STF_EMAIL || 'test_user@login.local',
|
||||
password: process.env.STF_PASSWORD,
|
||||
|
@ -34,7 +38,7 @@ module.exports.config = {
|
|||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
chromeOptions: {
|
||||
args: ['--test-type'] // Prevent security warning bug in ChromeDriver
|
||||
args: ['--test-type --no-sandbox'] // Prevent security warning bug in ChromeDriver
|
||||
}
|
||||
},
|
||||
chromeOnly: true,
|
||||
|
@ -45,16 +49,60 @@ module.exports.config = {
|
|||
|
||||
this.waitUrl = WaitUrl
|
||||
|
||||
jasmine.getEnv().addReporter(new HtmlReporter({
|
||||
baseDirectory: './res/test/test_out/screenshots'
|
||||
jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
|
||||
consolidateAll: true,
|
||||
savePath: reportsDirectory + '/xml',
|
||||
filePrefix: 'xmlOutput'
|
||||
}))
|
||||
|
||||
var fs = require('fs-extra')
|
||||
if (!fs.existsSync(dashboardReportDirectory)) {
|
||||
fs.mkdirs(dashboardReportDirectory)
|
||||
}
|
||||
|
||||
jasmine.getEnv().addReporter({
|
||||
specDone: function(result) {
|
||||
if (result.status === 'failed') {
|
||||
browser.getCapabilities().then(function(caps) {
|
||||
var browserName = caps.get('browserName')
|
||||
|
||||
browser.takeScreenshot().then(function(png) {
|
||||
var stream = fs.createWriteStream(dashboardReportDirectory + '/' +
|
||||
browserName + '-' + result.fullName + '.png')
|
||||
stream.write(new Buffer(png, 'base64'))
|
||||
stream.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
BrowserLogs({expectNoLogs: true})
|
||||
//FailFast()
|
||||
})
|
||||
},
|
||||
onComplete: function() {
|
||||
var browserName, browserVersion, platform, testConfig
|
||||
var capsPromise = browser.getCapabilities()
|
||||
|
||||
capsPromise.then(function(caps) {
|
||||
browserName = caps.get('browserName')
|
||||
browserVersion = caps.get('version')
|
||||
platform = caps.get('platform')
|
||||
|
||||
testConfig = {
|
||||
reportTitle: 'Protractor Test Execution Report',
|
||||
outputPath: dashboardReportDirectory,
|
||||
outputFilename: 'index',
|
||||
screenshotPath: './',
|
||||
testBrowser: browserName,
|
||||
browserVersion: browserVersion,
|
||||
modifiedSuiteName: false,
|
||||
screenshotsOnlyOnFailure: true,
|
||||
testPlatform: platform
|
||||
}
|
||||
new HTMLReport().from(reportsDirectory + '/xml/xmlOutput.xml', testConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue