Skip to main content
Glama
ViewHierarchy.kt3.62 kB
/* * * Copyright (c) 2022 mobile.dev inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ package maestro import maestro.UiElement.Companion.toUiElement @JvmInline value class ViewHierarchy(val root: TreeNode) { companion object { fun from(driver: Driver, excludeKeyboardElements: Boolean): ViewHierarchy { val deviceInfo = driver.deviceInfo() val root = driver.contentDescriptor(excludeKeyboardElements).let { val filtered = it.filterOutOfBounds( width = deviceInfo.widthGrid, height = deviceInfo.heightGrid ) filtered ?: it } return ViewHierarchy(root) } } fun isVisible(node: TreeNode): Boolean { if (!node.attributes.containsKey("bounds")) { return false } val center = node.toUiElement().bounds.center() val elementAtPosition = getElementAt(root, center.x, center.y) return node == elementAtPosition } fun refreshElement(node: TreeNode): TreeNode? { val matches = root.aggregate() .filter { (it.attributes - "bounds") == (node.attributes - "bounds") } if (matches.size != 1) { return null } return matches[0] } fun getElementAt( node: TreeNode, x: Int, y: Int ): TreeNode? { return node .children .asReversed() .asSequence() .mapNotNull { val elementWithinChild = if (it.children.isNotEmpty()) { getElementAt(it, x, y) } else { null } elementWithinChild ?: if (it.attributes.containsKey("bounds")) { val bounds = it.toUiElement().bounds if (bounds.contains(x, y)) { it } else { null } } else { null } } .firstOrNull() } fun aggregate(): List<TreeNode> { return root.aggregate() } } fun TreeNode.filterOutOfBounds(width: Int, height: Int): TreeNode? { if (attributes.containsKey("ignoreBoundsFiltering") && attributes["ignoreBoundsFiltering"] == "true") { return this } val filtered = children.mapNotNull { it.filterOutOfBounds(width, height) }.toList() // parent can have missing bounds val element = kotlin.runCatching { toUiElement() }.getOrNull() val visiblePercentage = element?.getVisiblePercentage(width, height) ?: 0.0 if (visiblePercentage < 0.1 && filtered.isEmpty()) { return null } return TreeNode( attributes = attributes, children = filtered, clickable = clickable, enabled = enabled, focused = focused, checked = checked, selected = selected ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mobile-dev-inc/Maestro'

If you have feedback or need assistance with the MCP directory API, please join our Discord server