1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-06 12:00:08 +02:00

Merge remote-tracking branch 'upstream/master' into market_name

This commit is contained in:
Karol Wrótniak 2019-09-19 23:47:12 +02:00
commit 96312dd453
No known key found for this signature in database
GPG key ID: DE1EED7FE32385FE
23 changed files with 874 additions and 50 deletions

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
8.16.1

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
nodejs 8.16.1

View file

@ -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

View file

@ -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)

View file

@ -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'`

View file

@ -325,7 +325,7 @@ ExecStart=/usr/bin/docker run --rm \
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
-e "SAML_ID_PROVIDER_ENTRY_POINT_URL=YOUR_ID_PROVIDER_ENTRY_POINT" \
-e "SAML_ID_PROVIDER_ISSUER=YOUR_ID_PROVIDER_ISSUER" \
-e "SAML_ID_PROVIDER_CERT_PATH=/etc/id_proider.cert" \
-e "SAML_ID_PROVIDER_CERT_PATH=/etc/id_provider.cert" \
-p %i:3000 \
openstf/stf:latest \
stf auth-saml2 --port 3000 \

View file

@ -3,7 +3,7 @@ var util = require('util')
var _ = require('lodash')
var Promise = require('bluebird')
var uuid = require('uuid')
var adbkit = require('adbkit')
var dbapi = require('../../../db/api')
var logger = require('../../../util/logger')
var datautil = require('../../../util/datautil')
@ -23,6 +23,7 @@ module.exports = {
, remoteConnectUserDeviceBySerial: remoteConnectUserDeviceBySerial
, remoteDisconnectUserDeviceBySerial: remoteDisconnectUserDeviceBySerial
, getUserAccessTokens: getUserAccessTokens
, addAdbPublicKey: addAdbPublicKey
}
function getUser(req, res) {
@ -400,3 +401,56 @@ function getUserAccessTokens(req, res) {
})
})
}
function addAdbPublicKey(req, res) {
var data = req.swagger.params.adb.value
adbkit.util.parsePublicKey(data.publickey)
.then(function(key) {
return dbapi.lookupUsersByAdbKey(key.fingerprint)
.then(function(cursor) {
return cursor.toArray()
})
.then(function(users) {
return {
key: {
title: data.title || key.comment
, fingerprint: key.fingerprint
}
, users: users
}
})
})
.then(function(data) {
if (data.users.length) {
return res.json({
success: true
})
}
else {
return dbapi.insertUserAdbKey(req.user.email, data.key)
.then(function() {
return res.json({
success: true
})
})
}
})
.then(function() {
req.options.push.send([
req.user.group
, wireutil.envelope(new wire.AdbKeysUpdatedMessage())
])
})
.catch(dbapi.DuplicateSecondaryIndexError, function() {
// No-op
return res.json({
success: true
})
}).catch(function(err) {
log.error('Failed to insert new adb key fingerprint: ', err.stack)
return res.status(500).json({
success: false
, message: 'Unable to insert new adb key fingerprint to database'
})
})
}

View file

@ -3,7 +3,8 @@ var path = require('path')
var events = require('events')
var express = require('express')
var SwaggerExpress = require('swagger-express-mw')
var swaggerExpress = require('swagger-express-mw')
var swaggerUi = require('swagger-tools/middleware/swagger-ui')
var cookieSession = require('cookie-session')
var Promise = require('bluebird')
var _ = require('lodash')
@ -67,10 +68,11 @@ module.exports = function(options) {
, swaggerFile: path.resolve(__dirname, 'swagger', 'api_v1.yaml')
}
SwaggerExpress.create(config, function(err, swaggerExpress) {
swaggerExpress.create(config, function(err, swaggerExpress) {
if (err) {
throw err
}
app.use(swaggerUi(swaggerExpress.runner.swagger))
swaggerExpress.register(app)
})

View file

@ -209,6 +209,41 @@ paths:
$ref: "#/definitions/ErrorResponse"
security:
- accessTokenAuth: []
/user/adbPublicKeys:
x-swagger-router-controller: user
post:
summary: Adb public keys
description: Add adb public key for current user
operationId: addAdbPublicKey
consumes:
- application/json
produces:
- application/json
tags:
- user
parameters:
- name: adb
in: body
schema:
type: object
required:
- publickey
properties:
publickey:
type: string
description: adb public key (~/.android/id_rsa.pub)
title:
type: string
description: By default will be extracted from public key
responses:
"200":
description: Add adb key response
default:
description: Unexpected Error
schema:
$ref: "#/definitions/ErrorResponse"
security:
- accessTokenAuth: []
/devices:
x-swagger-router-controller: devices
get:

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -88,6 +88,7 @@
"stf-syrup": "^1.0.0",
"stf-wiki": "^1.0.0",
"swagger-express-mw": "^0.7.0",
"swagger-tools": "^0.10.3",
"temp": "^0.8.1",
"transliteration": "^1.1.6",
"url-join": "1.1.0",
@ -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",
@ -119,13 +121,13 @@
"html-loader": "^0.4.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",
@ -137,8 +139,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",

View file

@ -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')
})
})

View file

@ -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')
})
})

View file

@ -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'))

View file

@ -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) {

View file

@ -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

View file

@ -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')
})
})
})

View file

@ -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

View file

@ -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)
})
}
}

647
yarn.lock

File diff suppressed because it is too large Load diff