Skip to content

Commit

Permalink
Merge pull request #108 from shamanec/experimental-grid
Browse files Browse the repository at this point in the history
* Added experimental Appium grid replacement for Selenium Grid
* UI improvements
  • Loading branch information
shamanec authored Jun 25, 2024
2 parents 9ebcf55 + 36bd35d commit 25d3422
Show file tree
Hide file tree
Showing 16 changed files with 852 additions and 241 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Supports Linux, macOS and Windows - notes below
- Backend
- Serving the web interface
- Proxy the communication to the provider instances
- Experimental Appium grid replacement for Selenium Grid
- Integrated with UI to reserve devices currently running Appium tests

### Provider features
- Straightforward dependencies setup
Expand Down
4 changes: 2 additions & 2 deletions common/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ func UpsertDeviceDB(device models.Device) error {
return nil
}

func GetDevices() []*models.Device {
func GetDevices() []models.Device {
// Access the database and collection
collection := MongoClient().Database("gads").Collection("devices")
latestDevices := []*models.Device{}
latestDevices := []models.Device{}

cursor, err := collection.Find(context.Background(), bson.D{{}}, options.Find())
if err != nil {
Expand Down
13 changes: 12 additions & 1 deletion common/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ type Device struct {
Model string `json:"model" bson:"model"` // common value - device model
Host string `json:"host" bson:"host"` // common value - IP address of the device host(provider)
Provider string `json:"provider" bson:"provider"` // common value - nickname of the device host(provider)
InUse bool `json:"in_use" bson:"-"` // UI value - if device is currently in use
ScreenWidth string `json:"screen_width" bson:"screen_width"` // common value - screen width of device
ScreenHeight string `json:"screen_height" bson:"screen_height"` // common value - screen height of device
HardwareModel string `json:"hardware_model,omitempty" bson:"hardware_model,omitempty"` // common value - hardware model of device
Expand Down Expand Up @@ -75,3 +74,15 @@ type ConnectedDevice struct {
UDID string `json:"udid" bson:"udid"`
IsConfigured bool `json:"is_configured" bson:"-"`
}

type LocalHubDevice struct {
Device Device `json:"info"`
SessionID string `json:"-"`
IsRunningAutomation bool `json:"is_running_automation"`
LastAutomationActionTS int64 `json:"last_automation_action_ts"`
InUse bool `json:"in_use"`
InUseBy string `json:"in_use_by"`
InUseTS int64 `json:"in_use_ts"`
AppiumNewCommandTimeout int64 `json:"appium_new_command_timeout"`
IsAvailableForAutomation bool `json:"is_available_for_automation"`
}
8 changes: 8 additions & 0 deletions docs/hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ If you want to work on the React UI with hot reload you need to add a proxy in `
4. Run `npm start`

### Additional notes
#### Experimental Appium grid
Using Selenium Grid 4 is a bit of a hassle and some versions do not work properly with Appium relay nodes. For this reason I created an experimental grid implementation into the hub itself. I haven't even read the Selenium Grid implementation and made up something myself - it might not work properly but could be the better alternative if it does work properly. The experimental grid was tested only using latest Appium and Selenium Java client versions and with TestNG. Tests can be executed sequentially or in parallel using TestNG with `methods` or `classes` with multiple threads. I assume it should support any type of session creation with any Appium language client

* The grid is accessible on your hub instance e.g. `http://192.168.1.6:10000/grid` and should be used as Appium/Selenium driver URL target. You just try to start a session as you usually do with Selenium Grid
* The grid allows targeting devices by UDID
* The grid allows targeting devices by `platformName`(iOS or Android) or `appium:automationName`(XCUITest or UiAutomator2) capabilities during session creation
* Additionally the grid allows filtering by `appium:platformVersion` capability which supports exact version e.g. `17.5.1` or a major version e.g. `17`, `11` etc

#### Selenium Grid
Devices can be automatically connected to Selenium Grid 4 instance. You need to create the Selenium Grid hub instance yourself and then set it up in the provider configuration to connect to it.
* Start your Selenium hub instance, e.g. `java -jar selenium.jar --host 192.168.1.6 --port 4444`
Expand Down
32 changes: 25 additions & 7 deletions hub/devices/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"GADS/common/models"
"fmt"
"strconv"
"sync"
"time"
)

Expand All @@ -27,22 +28,39 @@ func CalculateCanvasDimensions(device *models.Device) (canvasWidth string, canva
return
}

var LatestDevices []*models.Device
var HubDevicesMap = make(map[string]*models.LocalHubDevice)

// Get the latest devices information from MongoDB each second
func GetLatestDBDevices() {
LatestDevices = []*models.Device{}
var latestDBDevices []models.Device

for {
LatestDevices = db.GetDevices()
latestDBDevices = db.GetDevices()
for _, dbDevice := range latestDBDevices {
hubDevice, ok := HubDevicesMap[dbDevice.UDID]
if ok {
hubDevice.Device = dbDevice
} else {
HubDevicesMap[dbDevice.UDID] = &models.LocalHubDevice{
Device: dbDevice,
IsRunningAutomation: false,
IsAvailableForAutomation: true,
LastAutomationActionTS: 0,
}
}
}
time.Sleep(1 * time.Second)
}
}

func GetDeviceByUDID(udid string) *models.Device {
for _, device := range LatestDevices {
if device.UDID == udid {
return device
var getDeviceMu sync.Mutex

func GetHubDeviceByUDID(udid string) *models.LocalHubDevice {
getDeviceMu.Lock()
defer getDeviceMu.Unlock()
for _, hubDevice := range HubDevicesMap {
if hubDevice.Device.UDID == udid {
return hubDevice
}
}

Expand Down
5 changes: 3 additions & 2 deletions hub/gads-ui/src/components/DeviceControl/DeviceControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DialogProvider } from './SessionDialogContext';
import { api } from '../../services/api.js'

export default function DeviceControl() {
const { logout } = useContext(Auth)
const { logout, userName } = useContext(Auth)
const { id } = useParams();
const navigate = useNavigate();
const [deviceData, setDeviceData] = useState(null)
Expand Down Expand Up @@ -43,13 +43,14 @@ export default function DeviceControl() {
wsType = "wss"
}
let socketUrl = `${wsType}://${window.location.host}/devices/control/${id}/in-use`
// let socketUrl = `${wsType}://192.168.1.6:10000/devices/control/${id}/in-use`
in_use_socket = new WebSocket(socketUrl);
if (in_use_socket.readyState === WebSocket.OPEN) {
in_use_socket.send('ping');
}
const pingInterval = setInterval(() => {
if (in_use_socket.readyState === WebSocket.OPEN) {
in_use_socket.send('ping');
in_use_socket.send(userName);
}
}, 1000);

Expand Down
4 changes: 2 additions & 2 deletions hub/gads-ui/src/components/DeviceControl/StreamCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export default function StreamCanvas({ deviceData }) {
let deviceX = parseInt(deviceData.screen_width, 10)
let deviceY = parseInt(deviceData.screen_height, 10)
let screen_ratio = deviceX / deviceY
let canvasHeight = 850
let canvasWidth = 850 * screen_ratio
let canvasHeight = (window.innerHeight * 0.7)
let canvasWidth = (window.innerHeight * 0.7) * screen_ratio

const streamData = {
udid: deviceData.udid,
Expand Down
84 changes: 0 additions & 84 deletions hub/gads-ui/src/components/DeviceSelection/Device.js

This file was deleted.

45 changes: 27 additions & 18 deletions hub/gads-ui/src/components/DeviceSelection/DeviceSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';
import { OSFilterTabs, DeviceSearch } from './Filters'
import { DeviceBox } from './Device'
import { Auth } from '../../contexts/Auth';
import { api } from '../../services/api.js'
import NewDeviceBox from "./NewDeviceBox";

export default function DeviceSelection() {
// States
Expand All @@ -26,7 +26,7 @@ export default function DeviceSelection() {
const open = true

// Authentication and session control
const {logout} = useContext(Auth)
const { logout } = useContext(Auth)

function CheckServerHealth() {
let url = `/health`
Expand Down Expand Up @@ -68,13 +68,13 @@ export default function DeviceSelection() {

evtSource.onmessage = (message) => {
let devicesJson = JSON.parse(message.data)
console.log(devicesJson)
setDevices(devicesJson);
}

// If component unmounts close the websocket connection
return () => {
if (evtSource) {
console.log('component unmounted')
evtSource.close()
}
}
Expand Down Expand Up @@ -167,36 +167,45 @@ function OSSelection({ devices, handleAlert }) {
) : (
<TabPanel
value='{currentTabIndex}'
style={{ height: "800px", overflowY: "auto"}}
style={{ height: "80vh", overflowY: "auto" }}
>
<Grid
id='devices-container'
container spacing={2}
style={{
marginBottom: '10px'
}}
>
{
devices.map((device) => {
if (currentTabIndex === 0) {
return (
<DeviceBox
device={device}
handleAlert={handleAlert}
/>
<Grid item>
<NewDeviceBox
device={device}
handleAlert={handleAlert}
/>
</Grid>
)

} else if (currentTabIndex === 1 && device.os === 'android') {
} else if (currentTabIndex === 1 && device.info.os === 'android') {
return (
<DeviceBox
device={device}
handleAlert={handleAlert}
/>
<Grid item>
<NewDeviceBox
device={device}
handleAlert={handleAlert}
/>
</Grid>
)

} else if (currentTabIndex === 2 && device.os === 'ios') {
} else if (currentTabIndex === 2 && device.info.os === 'ios') {
return (
<DeviceBox
device={device}
handleAlert={handleAlert}
/>
<Grid item>
<NewDeviceBox
device={device}
handleAlert={handleAlert}
/>
</Grid>
)
}
})
Expand Down
Loading

0 comments on commit 25d3422

Please sign in to comment.