«En cada versión, revisar a mano todas las pantallas nos toma dos días completos»; «arreglamos algo el mes pasado y, justo al lado, otra pantalla se rompió, y lo descubrió el cliente»; «el equipo web automatiza las pruebas de regresión con Selenium o Playwright, pero la aplicación de escritorio sigue sin tocarse». Este tipo de consulta es habitual entre equipos que mantienen desde hace tiempo aplicaciones empresariales WinForms/WPF.
Al mismo tiempo, escuchamos con la misma frecuencia el relato inverso: empezar con entusiasmo a escribir pruebas para todas las pantallas y, un año después, abandonarlas por no poder sostener el mantenimiento. Si se elige mal la herramienta y no se traza bien el límite de «hasta dónde llegar», las pruebas de UI automatizadas salen más caras que la verificación manual. Pero si se entiende el mecanismo, se diseñan para que no se rompan y se limita su cobertura a pruebas de humo, se convierten en una inversión que cambia «dos días antes de cada versión» por «20 minutos desatendidos cada noche». En este artículo repasamos el mecanismo de base, UI Automation (UIA); el estado actual de las herramientas (FlaUI, WinAppDriver, Appium); la implementación con FlaUI; el diseño para que no se rompa; y la ejecución desatendida en CI: el patrón completo que usamos en proyectos reales.
1. Antes que nada, la conclusión
Resumido en una frase: «busque por AutomationId, opere con los patrones de control y limite la cobertura a pruebas de humo». A continuación, el detalle.
- Las pruebas de UI automatizadas son el nivel más alto de la pirámide de pruebas. Son lentas, frágiles y cuesta identificar la causa de un fallo, así que no sustituyen a las pruebas unitarias ni a las de integración. La lógica se protege en las capas inferiores, y las pruebas de UI se limitan a confirmar, como pruebas de humo, que «la aplicación arranca y los flujos principales funcionan» (capítulo 6).
- La base del mecanismo es Windows UI Automation (UIA). Se buscan elementos en un árbol de automatización cuya raíz es el escritorio, se identifican mediante propiedades como AutomationId / Name, y se operan con patrones de control (Invoke, Value, SelectionItem, etc.). 12
- Antes de escribir código, hay que confirmar cómo se ven los elementos de la aplicación objetivo con inspect.exe o Accessibility Insights for Windows. inspect es una herramienta clásica incluida en el Windows SDK; hoy en día, Accessibility Insights es la recomendación oficial. 3
- La herramienta que recomendamos es FlaUI junto con xUnit/NUnit. FlaUI es un OSS con licencia MIT que envuelve de forma ligera UIA, es compatible con UIA2 y UIA3, y se sigue manteniendo activamente. 4
- WinAppDriver, que en su momento fue la opción oficial de Microsoft, tiene su última versión estable detenida en la v1.2.1 de noviembre de 2020, y como el servidor no tiene el código fuente público, la comunidad tampoco puede corregirlo. No recomendamos adoptarlo en proyectos nuevos (capítulo 3). 5
- El 80% de la resistencia a roturas depende de la convención de asignar AutomationId desde el desarrollo. En WPF es
x:NameoAutomationProperties.AutomationId; en WinForms,Name/AccessibleName. Se prohíbe la búsqueda basada en el texto visible (Name), el clic por coordenadas yThread.Sleep, y se escribe con espera condicional (Retry) y el patrón Page Object (capítulos 4 y 5). 67 - La ejecución desatendida en CI exige una sesión de escritorio interactiva. No funciona con un agente configurado como servicio, y también falla si la pantalla se bloquea o se desconecta el RDP. La forma básica es configurar un ejecutor autoalojado con inicio de sesión automático (autologon) (capítulo 7). 8
2. El mecanismo ── árbol, propiedades y patrones de UI Automation
2.1 El árbol de automatización
Las herramientas de prueba de UI automatizadas no reconocen la pantalla como una imagen. Windows cuenta con UI Automation (UIA), la base oficial que permite a las tecnologías de asistencia, como los lectores de pantalla, leer y operar la UI de una aplicación desde código, y la automatización de pruebas circula por ese mismo carril. 1
En UIA, con el escritorio como raíz, todas las ventanas abiertas y los controles que contienen se exponen en una estructura de árbol. Un botón, un cuadro de texto o una fila de una cuadrícula son todos elementos de automatización dentro del árbol. Además de la vista raw, que incluye todos los elementos, el árbol ofrece vistas filtradas: la vista de control, limitada a los elementos que pueden ser objeto de operación, y la vista de contenido, limitada al contenido en sí. 1 En la práctica, el código de prueba recorre básicamente la vista de control para buscar elementos.
Las tres vistas no son alternativas paralelas: son distintos grados de filtro aplicados a un mismo árbol. La relación de inclusión queda anidada.
flowchart TB
accTitle: Relación de inclusión entre las vistas raw, control y content del árbol de UIA
accDescr: Diagrama que muestra que la vista content está contenida en la vista control, y la vista control está contenida en la vista raw, con ejemplos de qué tipo de elemento aparece en cada nivel
subgraph RAW["Vista raw ── todos los elementos del árbol"]
subgraph CTRL["Vista control ── elementos que pueden ser objeto de operación"]
subgraph CONT["Vista content ── elementos que transmiten información al usuario"]
C1["Botón SaveButton"]
C2["Cuadro de texto CustomerNameBox"]
C3["Elemento de lista Comercial de Prueba"]
end
K1["Barra de título"]
K2["Barra de desplazamiento"]
K3["Marco de un grupo"]
end
R1["Línea divisoria decorativa"]
R2["Elemento intermedio solo de diseño"]
end
La forma de leerlo es: «lo que está en la vista content también está en la vista control, y lo que está en la vista control también está en la vista raw». Los elementos que suele buscar una prueba están casi siempre en los dos círculos internos. En cambio, si toma como punto de apoyo de la búsqueda un elemento que solo aparece en la vista raw (por ejemplo, un contenedor exclusivo de diseño), la prueba se romperá con un pequeño cambio en la construcción de la UI. En inspect.exe, esta vista se puede alternar desde el menú Options, con «Raw View», «Control View» y «Content View», así que al hacer el inventario en la sección 2.3 conviene comprobar hasta qué vista sobrevive el elemento que quiere buscar; eso facilita el diseño de las condiciones de búsqueda. 3
Aquí conviene tener presente que solo se puede operar lo que está expuesto en el árbol, no lo que se ve a simple vista. Una pantalla armada con controles estándar aparece con nitidez en el árbol, pero una lista dibujada a mano (owner-draw), un área de dibujo hecha con una biblioteca gráfica o parte de una cuadrícula de terceros pueden verse en el árbol como «un único elemento» sin más detalle. Esa apariencia es, directamente, el límite superior de la cobertura de las pruebas de UI automatizadas. Por eso la comprobación del árbol antes de escribir código (sección 2.3) es el primer paso.
2.2 Propiedades y patrones de control
La propiedad es la pista que permite identificar el elemento buscado dentro del árbol. En la práctica se usan estas cuatro:
| Propiedad | Contenido | Papel en las pruebas |
|---|---|---|
| AutomationId | Identificador asignado por el desarrollador. No depende del idioma (la configuración regional) | La opción principal para la búsqueda. Debe ser único entre los elementos hermanos 6 |
| Name | Nombre derivado del texto visible (por ejemplo, la etiqueta de un botón) | Fácil de entender para una persona, pero se rompe con cambios de texto o al internacionalizar |
| ControlType | Tipo de control: Button, Edit, ComboBox, etc. | Ayuda a acotar la búsqueda |
| ClassName | Nombre de la clase de implementación (por ejemplo, la clase de un control de WinForms) | Último recurso. Frágil ante cambios de implementación |
AutomationId está definida oficialmente como una propiedad que «debe conservar el mismo valor aunque cambie la configuración regional» y que «debe ser única entre los elementos hermanos»; es la propiedad dedicada a que las pruebas de UI automatizadas encuentren elementos de forma estable a través de idiomas y versiones. 6 Dicho de otro modo, una aplicación sin AutomationId asignado obliga a las pruebas a apoyarse en pistas frágiles, como el texto visible o la posición dentro del árbol. Este es el tema central del capítulo 5.
El medio para operar el elemento encontrado es el patrón de control. UIA expone las capacidades funcionales de un control —«se puede hacer clic», «tiene un valor», «se puede seleccionar»— como un conjunto de patrones independientes del tipo de control. La propia documentación explica que «la relación entre los patrones de control y la UI es igual a la relación entre un objeto COM y sus interfaces»: el diseño consiste en preguntarle al elemento qué patrones implementa y operarlo a través de ese patrón. 2 Para quienes ya conocen COM, se puede pensar como el equivalente en UI de QueryInterface. Los patrones principales son estos:
| Patrón | Qué permite hacer | Controles típicos |
|---|---|---|
| Invoke | Ejecutar la acción predeterminada (equivalente a un clic) | Botones, elementos de menú |
| Value | Obtener y establecer un valor | Cuadros de texto |
| SelectionItem / Selection | Seleccionar un elemento y obtener el estado de selección | Listas, cuadros combinados, pestañas |
| Toggle | Alternar entre activado y desactivado | Casillas de verificación |
| ExpandCollapse | Expandir y contraer | Cuadros combinados, elementos de árbol |
| Text | Leer el contenido de texto | Documentos, texto enriquecido |
| Window | Maximizar, minimizar, cerrar | Ventanas de nivel superior |
| Scroll / ScrollItem | Desplazar, llevar un elemento a una posición visible | Listas, cuadrículas |
El código de prueba que «hace clic en un botón» internamente obtiene el patrón Invoke de ese elemento y llama a Invoke(). En lugar de calcular coordenadas y enviar eventos de mouse, se invoca la operación que el propio control expone: esa diferencia es la base de una prueba estable que no depende de la posición de la ventana ni del DPI.
2.3 Confirmar «lo que se ve» con inspect.exe y Accessibility Insights
La forma más directa de saber con qué AutomationId, Name, ControlType y patrones se exponen los elementos de la aplicación objetivo es verlo con una herramienta real.
- inspect.exe: herramienta clásica incluida en el Windows SDK (se encuentra en
bin\<versión>\<plataforma>de la instalación del SDK). Al seleccionar un elemento con el mouse o con el foco del teclado, se muestra la lista de propiedades y patrones de UIA, y también se puede recorrer el árbol. Sin embargo, está posicionada oficialmente como «herramienta clásica», y se recomienda migrar a Accessibility Insights. 3 - Accessibility Insights for Windows: la herramienta que Microsoft recomienda actualmente. Su función Live Inspect, que muestra las propiedades UIA del elemento con solo pasar el mouse o mover el foco, es muy práctica, y además incluye una verificación automática de accesibilidad (FastPass). 3
- FlaUInspect: el inspector incluido en el propio proyecto FlaUI. Permite ver el árbol desde la perspectiva de UIA2 y de UIA3, que son las que FlaUI usa realmente, así que conviene instalarlo también si va a escribir pruebas con FlaUI. 4
Qué mirar en inspect.exe
En lugar de una captura de pantalla, dejamos anotado qué aparece en cada parte de la ventana. inspect.exe se encuentra en bin\<versión>\<plataforma> de la instalación del Windows SDK, y normalmente no hace falta ejecutarlo como administrador. Al iniciarse, por defecto sigue el foco del teclado o del mouse, y la información del elemento seleccionado aparece a la derecha. 3
| Parte de la ventana | Qué muestra | Qué hay que observar en las pruebas |
|---|---|---|
| Vista de árbol (izquierda) | La jerarquía de elementos con el escritorio como raíz. Aquí se recorre para confirmar la relación entre padres e hijos | Si el elemento buscado aparece en el árbol y bajo qué padre está |
| Vista de datos (derecha) | Lista con nombre y valor de las propiedades UIA del elemento seleccionado | AutomationId, Name, ControlType, ClassName, IsEnabled, BoundingRectangle |
| Menú Options | Alterna el modo de visualización y la forma de seguimiento | «UI Automation Mode» (no «MSAA Mode»), «Raw View» / «Control View» / «Content View», «Watch Focus» / «Watch Cursor», «Show Highlight Rectangle» |
| Menú Action | Permite ejecutar métodos de UIA sobre el elemento seleccionado | En modo UI Automation, los patrones de control (sección 2.2) que implementa ese elemento aparecen directamente como opciones |
| Options > Settings | Selección de qué propiedades mostrar en la vista de datos | Cuando hay demasiados elementos, permite acotar la vista. «Display unsupported properties» también muestra las propiedades no compatibles |
En la práctica, se usa así: primero se confirma que está activo el «UI Automation Mode», se activa «Watch Focus» y se opera la aplicación objetivo con el teclado; cada vez que el foco cambia, la vista de datos se actualiza. Ahí se comprueba si AutomationId está vacío. Si lo está, primero hay que resolver la modificación del lado de la aplicación descrita en el capítulo 5. Después se abre el menú Action y se confirma si Invoke aparece en el botón que se quiere pulsar, o si Value aparece en el campo que se quiere completar. Lo que aparece ahí es exactamente lo que el código de prueba puede invocar. Para capturar elementos que no reciben el foco, como un menú dinámico o un tooltip, conviene cambiar a «Watch Cursor». 3
Como regla empírica: lo primero que hay que hacer al introducir pruebas de UI automatizadas no es «escribir código de prueba», sino abrir las pantallas principales con una herramienta tipo inspect y hacer el inventario de cuánto AutomationId ya está asignado. Si el resultado es escaso, suele salir más rápido empezar por la modificación del lado de la aplicación (capítulo 5).
3. Las opciones de herramientas ── por qué recomendamos FlaUI y el estado actual de WinAppDriver
Se puede llamar a UIA directamente por COM, pero en la práctica se usa una biblioteca envoltorio. Organizamos aquí las opciones y su estado actual a 2026, con hechos verificados.
| Herramienta | Forma | Estado actual (2026) | Criterio para adoptarla en un proyecto nuevo |
|---|---|---|---|
| FlaUI | Biblioteca .NET (MIT) | OSS mantenido activamente. La v5.0.0 se publicó en febrero de 2025 4 | ◎ Primera opción |
| WinAppDriver | Servidor con protocolo WebDriver (Microsoft) | Última versión estable v1.2.1 de noviembre de 2020. La v1.3 sigue siendo un RC de julio de 2020. Más de 1100 issues sin resolver 5 | △ Prácticamente detenido. Evitar en proyectos nuevos |
| Appium Windows Driver | Driver de Appium para Windows | Usa WinAppDriver internamente, por lo que hereda directamente las limitaciones anteriores 9 | △ Solo si ya cuenta con activos de Appium |
| Pruebas Coded UI | Función de Visual Studio | Obsoleta desde VS 2019, eliminada en VS 2026 10 | × Objeto de migración |
3.1 FlaUI ── hoy es la envoltura ligera de UIA más práctica
FlaUI es una biblioteca .NET que da soporte a las pruebas de UI automatizadas de aplicaciones de Windows (Win32, WinForms, WPF, apps de la Store) y está diseñada como una envoltura de la biblioteca UIA nativa de Microsoft. 4 El paquete se compone del núcleo común FlaUI.Core y de FlaUI.UIA2 / FlaUI.UIA3, que se eligen según qué implementación de UIA se use.
Las FAQ de FlaUI explican con claridad cuándo usar UIA2 y cuándo UIA3. UIA2 es solo una implementación administrada, no admite funciones nuevas como el touch y no se lleva bien con WPF ni con las apps de la Store. UIA3 es la implementación más reciente, óptima para WPF y para apps de la Store, pero en aplicaciones WinForms puede toparse con defectos que UIA2 no tiene. 4 Es decir, el criterio práctico es: probar primero UIA3 para WPF y UIA2 para WinForms, y usar el que resulte estable en la aplicación real. La ventaja de que FlaUI sea una envoltura ligera es que se pueden instalar ambos paquetes desde NuGet y alternar entre ellos para probar.
FlaUI no incluye un framework de pruebas propio, así que se combina con xUnit, NUnit o MSTest y se escribe como un proyecto de pruebas normal. Que corra en el mismo ejecutor de pruebas y en el mismo pipeline de CI que las pruebas unitarias resulta muy útil en la operación diaria.
3.2 WinAppDriver ── siendo francos, está prácticamente detenido
WinAppDriver es un servidor de pruebas de UI de Microsoft que permite operar aplicaciones de Windows con el mismo protocolo WebDriver que usa Selenium, y en su momento fue la opción predilecta. Cuando las pruebas Coded UI de Visual Studio quedaron obsoletas, el propio Microsoft llegó a recomendar como migración «Selenium para la Web, y Appium más WinAppDriver para el escritorio y UWP». 10
Sin embargo, al revisar el estado actual en GitHub, la última versión estable, v1.2.1, se publicó en noviembre de 2020, y no ha salido ninguna versión estable posterior. La v1.3 sigue siendo el Release Candidate (v1.2.99) de julio de 2020 y nunca se convirtió en versión final, y hay más de 1100 issues sin resolver. 5 Lo más problemático es que en el repositorio de GitHub solo hay documentación, ejemplos y el rastreador de issues: el código fuente del servidor en sí no está publicado. Es decir, la comunidad tampoco puede corregir sus errores. Nuestra evaluación es que en 2026 no es una herramienta que convenga adoptar en un proyecto nuevo solo porque «es oficial, así que da confianza». Si ya tiene activos de prueba funcionando sobre WinAppDriver no hace falta descartarlos de inmediato, pero conviene dejar de ampliarlos y orientar las pruebas de los nuevos flujos hacia FlaUI.
3.3 Appium Windows Driver y otras opciones
Appium Windows Driver (appium-windows-driver) es el driver que permite operar aplicaciones de Windows desde Appium, pero en la práctica es «una interfaz hacia el WinAppDriver que provee Microsoft»: todo el procesamiento pesado corre por cuenta de WinAppDriver. 9 Por lo tanto, hereda directamente el estancamiento de WinAppDriver. Sigue siendo una opción para equipos que ya unificaron sus pruebas móviles y web en Appium, o que quieren escribir el código de prueba en Java o Python, pero en ese caso conviene tener claras de antemano las limitaciones y la falta de perspectiva de futuro que arrastra de WinAppDriver.
Las aplicaciones de WinUI 3 (Windows App SDK) también son compatibles con UIA, así que pueden probarse con FlaUI (UIA3). Sin embargo, en comparación con WinForms/WPF hay menos experiencia acumulada, y las peculiaridades de cómo se ve cada control cobran aún más importancia al revisarlas de antemano con herramientas tipo inspect. La elección del propio framework de UI está organizada en «Cómo elegir entre WinForms/WPF/WinUI - tabla de decisión práctica».
4. Implementación mínima con FlaUI ── inicio, búsqueda, operación y verificación
Dejemos la teoría y veamos código que funciona. Antes, resumimos aquí los prerrequisitos que aparecen dispersos en el resto del texto.
Lo que hay que preparar
| Tipo | Qué preparar | Nota |
|---|---|---|
| Objeto de prueba | El binario compilado de la aplicación (la ruta completa del exe) | Tener una opción de inicio que permita sustituir el archivo de configuración o la base de datos por una versión de prueba facilita mucho las cosas (sección 5.4) |
| SDK | .NET SDK (se recomienda 8 o superior) | Como UIA es exclusivo de Windows, el destino del proyecto de pruebas debe ser net8.0-windows |
| NuGet | FlaUI.UIA3 (para WPF) o FlaUI.UIA2 (para WinForms) |
El núcleo común FlaUI.Core se instala junto como dependencia. Se pueden instalar ambos y alternar (sección 3.1) 4 |
| NuGet | xunit más xunit.runner.visualstudio |
Se instalan con dotnet new xunit. También sirven NUnit o MSTest |
| Herramienta de inspección | inspect.exe o Accessibility Insights for Windows | Para hacer el inventario de cómo se ve la aplicación objetivo antes de escribir código (sección 2.3) 3 |
| Entorno de ejecución | Windows con pantalla. Mientras se ejecuta, nadie debe tocar esa máquina | Las condiciones para llevarlo a ejecución desatendida están en el capítulo 7 |
Crear el proyecto son tres comandos.
dotnet new xunit -o OrderManager.UiTests
cd OrderManager.UiTests
dotnet add package FlaUI.UIA3
Hay que cambiar el TargetFramework del .csproj generado a net8.0-windows (porque UIA es exclusivo de Windows). Al mismo tiempo, como las pruebas de UI deben ejecutarse una por una y en serie en cada máquina (sección 7.2), conviene desactivar desde el principio la ejecución en paralelo de xUnit.
// Escribir una sola vez en algún lugar del proyecto de pruebas (por ejemplo, en AssemblyInfo.cs)
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
A partir de aquí, es un proyecto de pruebas normal. No hace falta un ejecutor especial ni un tipo de proyecto especial.
4.1 La forma básica de una prueba de humo
Una prueba del flujo «iniciar la aplicación, abrir el diálogo de alta de pedidos, guardar un registro y ver el resultado en el estado» se escribe así.
using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Tools;
using FlaUI.UIA3;
using Xunit;
public class OrderSmokeTest
{
[Fact]
public void 受注登録_主要導線が通る()
{
using var app = Application.Launch(@"C:\App\OrderManager.exe");
using var automation = new UIA3Automation();
try
{
// Espera hasta que aparece la ventana principal
var window = app.GetMainWindow(automation);
// Busca el elemento por AutomationId y lo opera como Button
window.FindFirstDescendant(cf => cf.ByAutomationId("NewOrderButton"))
?.AsButton().Invoke();
// El diálogo se abre de forma asíncrona, así que se espera «hasta que aparezca» con una condición
var dialog = Retry.WhileNull(
() => window.FindFirstDescendant(
cf => cf.ByAutomationId("OrderDialog"))?.AsWindow(),
timeout: TimeSpan.FromSeconds(5)).Result;
Assert.NotNull(dialog);
// Entrada a través del patrón Value (no es emulación de teclado)
dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox"))
.AsTextBox().Text = "Comercial de Prueba";
dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton"))
.AsButton().Invoke();
// La finalización del guardado también se verifica con espera condicional sobre el estado mostrado
var saved = Retry.WhileFalse(
() => window.FindFirstDescendant(
cf => cf.ByAutomationId("StatusLabel"))
?.Name.Contains("Se guardó") == true,
timeout: TimeSpan.FromSeconds(10));
Assert.True(saved.Success);
}
finally
{
app.Close();
}
}
}
Solo hay cuatro componentes.
- Inicio:
Application.Launchinicia el proceso (también se puede conectar a una aplicación ya iniciada conApplication.Attach).GetMainWindowespera hasta poder obtener la ventana principal. - Búsqueda:
FindFirstDescendant(cf => cf.ByAutomationId(...))es la búsqueda en el árbol.cfes una fábrica de condiciones, con la que también se pueden combinar condicionesByName/ByControlType/And. Como se explicó antes, la clave de búsqueda principal es AutomationId. - Operación: el elemento encontrado se convierte a una envoltura tipada como
AsButton()/AsTextBox()/AsComboBox(), y se opera desde ahí.Invoke()corresponde al patrón Invoke, y la propiedadText, al patrón Value: los patrones de control de la sección 2.2 están directamente detrás. - Verificación: se hace un assert sobre el resultado que aparece en la UI (una etiqueta, la cantidad de filas de una lista, el título de la ventana, etc.). Si se quiere revisar el contenido de la base de datos, el código de prueba puede leerla directamente.
4.2 Espere con Retry ── si escribe Sleep, ya perdió
La mayor fuente de inestabilidad en las pruebas de UI (las llamadas pruebas «flaky») es el timing. Hasta que se abre un diálogo, hasta que se cargan los datos, hasta que un botón se habilita: la UI cambia siempre de forma asíncrona, y si el código de prueba asume que «ya debería estar visible», el resultado es una prueba que solo falla en las máquinas más lentas.
Aun así, agregar Thread.Sleep(3000) es la peor solución. En una máquina rápida espera de más sin necesidad, en una lenta no alcanza y la prueba falla, y el tiempo total de ejecución solo se va acumulando. La respuesta es la espera condicional, es decir, «hacer polling hasta que se cumpla la condición y cortar por timeout», y FlaUI tiene una clase dedicada, Retry. 4
// Espera hasta 5 segundos a que deje de ser null (= a que aparezca el elemento)
var element = Retry.WhileNull(
() => window.FindFirstDescendant(cf => cf.ByAutomationId("ResultGrid")),
timeout: TimeSpan.FromSeconds(5),
interval: TimeSpan.FromMilliseconds(200),
throwOnTimeout: true).Result;
// Espera hasta que la condición sea true (= hasta que el botón se habilite)
Retry.WhileFalse(
() => saveButton.IsEnabled,
timeout: TimeSpan.FromSeconds(5),
throwOnTimeout: true);
Retry.WhileNull / WhileFalse / WhileTrue / WhileException, entre otros, están disponibles, y permiten indicar el timeout, el intervalo de polling y si se debe lanzar una excepción al agotar el tiempo. 4 Un punto a tener en cuenta: a partir de la versión 2.0, FlaUI eliminó el reintento implícito de los métodos de tipo Find, adoptando la política de que «el código de prueba indica explícitamente dónde esperar». 4 FindFirstDescendant solo ve «el árbol en este instante exacto», así que conviene establecer como norma envolver siempre en Retry la búsqueda de elementos que aparecen de forma asíncrona. Cabe señalar que la idea de «plantear una condición de espera en lugar de resignarse con un Sleep» no es exclusiva de las pruebas de UI: es una práctica habitual en toda la programación de Windows (véase «Por qué en Windows conviene priorizar la espera por eventos frente a Sleep(1)»).
5. Diseño para que no se rompa ── convenciones del lado de la aplicación y del lado de la prueba
Que una prueba de UI termine «abandonada por no poder sostener el mantenimiento» suele deberse a estas cuatro causas: la búsqueda basada en el texto visible, el clic por coordenadas, el Sleep y la falta de una estructura compartida. Las resolvemos una por una con convenciones.
5.1 Asigne siempre AutomationId desde el desarrollo
Esto es lo más importante. La resistencia a roturas de una prueba se decide, antes que por cómo se escribe el código de prueba, por si la aplicación expone identificadores estables. AutomationId es exactamente la propiedad pensada para eso: no depende de la configuración regional y debe ser única entre los elementos hermanos, según la propia especificación. 6
En WPF, el elemento al que se le asigna x:Name usa ese mismo valor como identificador del lado de UIA, así que un control ya nombrado se puede probar sin trabajo adicional. Cuando se quiere asignarlo de forma explícita, por ejemplo dentro de una plantilla de datos, se configura la propiedad adjunta AutomationProperties.AutomationId. 67
<!-- x:Name se usa directamente como identificador -->
<Button x:Name="SaveButton" Content="Guardar" Click="OnSave" />
<!-- Dentro de una plantilla, entre otros casos, se declara explícitamente AutomationProperties.AutomationId -->
<Button AutomationProperties.AutomationId="DeleteRowButton"
Content="Eliminar"
Command="{Binding DeleteCommand}" />
En WinForms, el Control.Name que se asigna en el diseñador (ese nombre con el que se cambia button1 por saveButton) es el que se usa del lado de UIA para la identificación. Según nuestra experiencia, en la mayoría de los formularios donde el Name se asignó siguiendo la convención se puede buscar directamente por AutomationId, pero como la apariencia varía según la generación del framework y el control, confirme siempre el AutomationId real con una herramienta tipo inspect antes de usarlo como clave de búsqueda en la prueba. Además, la propiedad Name de UIA, que es el nombre que leerán los lectores de pantalla, en muchos controles reutiliza la propiedad Text, pero en tipos que no la reutilizan, como TextBox o ListView, hace falta configurar explícitamente AccessibleName. 11 El hecho de que preparar el AutomationId sea exactamente el mismo trabajo que la accesibilidad, y no algo pensado únicamente para las pruebas, es un buen argumento a la hora de conseguir presupuesto internamente.
La convención puede ser simple; en nuestro caso agregamos estas dos líneas a la guía de codificación:
- A los controles que se coloquen en pantalla, siempre que puedan ser objeto de operación o verificación, se les asigna un
Namesignificativo (WinForms) o unx:Name/AutomationProperties.AutomationId(WPF) - Un identificador que ya haya sido referenciado por una prueba se renombra al mismo tiempo del lado de la prueba (el identificador se trata como una API pública)
5.2 Prohibido el clic por coordenadas
Una operación del tipo «hacer clic en las coordenadas de pantalla (830, 412)» se rompe con cualquier cambio en la posición de la ventana, la resolución, el escalado por DPI, el tema o la configuración de fuente. El DPI en particular genera con frecuencia el patrón «funciona en local pero falla en CI» por diferencias de entorno, como una máquina de desarrollo al 100% frente a una máquina de CI al 150% (el mecanismo del DPI está explicado en «Alto DPI en WinForms»). Como vimos en el capítulo 2, operar a través de los patrones de control de UIA no depende de coordenadas. FlaUI también ofrece una API para manejar el mouse directamente, pero establecemos como norma usarla solo para operaciones que no se puedan expresar con un patrón, como arrastrar y soltar o un lienzo de dibujo. Incluso en esos casos, la posición relativa se calcula a partir del BoundingRectangle del elemento, nunca con coordenadas de pantalla.
5.3 Concentre la estructura en un solo lugar con el patrón Page Object
Si el código de búsqueda del tipo FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")) se escribe directamente dentro del cuerpo de la prueba, cualquier cambio en la composición de la pantalla obliga a corregir todas las pruebas. La solución estándar es el patrón Page Object: se crea «una clase por cada pantalla (o diálogo)» y se encierra ahí la búsqueda y la operación de los elementos.
public sealed class OrderDialogPage
{
private readonly Window _dialog;
public OrderDialogPage(Window dialog) => _dialog = dialog;
// La búsqueda de elementos se escribe únicamente dentro de esta clase
private TextBox CustomerName =>
_dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")).AsTextBox();
private Button Save =>
_dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton")).AsButton();
private Label Status =>
_dialog.FindFirstDescendant(cf => cf.ByAutomationId("StatusLabel")).AsLabel();
// Desde la prueba solo se ve la operación en «lenguaje de negocio»
public void Register(string customerName)
{
CustomerName.Text = customerName;
Save.Invoke();
// La búsqueda del elemento y la comprobación de null también se hacen dentro del retry.
// En pantallas donde la etiqueta de estado se genera o se vuelve a dibujar tras el guardado,
// es normal que la búsqueda dé null o lance una excepción por un instante
// (sin ignoreException, la primera excepción haría fallar la prueba de inmediato)
Retry.WhileFalse(() => Status?.Name.Contains("Se guardó") == true,
timeout: TimeSpan.FromSeconds(10), throwOnTimeout: true,
ignoreException: true);
}
}
El cuerpo de la prueba pasa a poder escribirse en lenguaje de negocio, como new OrderDialogPage(dialog).Register("Comercial de Prueba"), y el impacto de un cambio de pantalla se resuelve modificando un único lugar, el Page Object. Si la aplicación tiene más de 10 pantallas y se mantienen pruebas de UI a largo plazo, este patrón es, en la práctica, imprescindible. Un único punto de atención: los elementos referenciados desde una condición de espera, como Status en el ejemplo, deben volver a buscarse cada vez a través de la propiedad, tal como se muestra. Si el resultado de la búsqueda se guarda en caché en un campo, se puede terminar sosteniendo un elemento antiguo que ya desapareció por un redibujado, y esperar hasta el timeout sin sentido.
5.4 Independencia de las pruebas ── no arrastre estado entre ellas
Cada prueba de UI debe ser independiente. Si crea una dependencia de orden del tipo «la prueba 3 asume los datos que creó la prueba 2», un solo fallo se propaga en cadena y además deja de poder reordenar las pruebas. Los principios son estos:
- Cada prueba (o clase de pruebas) inicia su propia instancia de la aplicación y la cierra sin falta al terminar (con
finallyo con un fixtureIDisposable) - Los datos previos los prepara la propia prueba. Tener del lado de la aplicación una opción de inicio que permita sustituir el archivo de configuración y la base de datos por versiones de prueba facilita mucho las cosas
- No olvide la limpieza tras un fallo. Un proceso que no se cerró o los restos de un diálogo modal pueden ser la causa del fallo de la prueba siguiente (capítulo 7)
6. Hasta dónde cubrir con pruebas de UI ── una línea centrada en las pruebas de humo
En cuanto se tienen las herramientas, dan ganas de probar todas las pantallas, pero aquí está la divisoria de aguas. Las pruebas de UI son órdenes de magnitud más lentas que las unitarias (de segundos a decenas de segundos por prueba), más frágiles (hay que ajustarlas con cada cambio de UI) y cuestan más tiempo de diagnosticar cuando fallan (¿es un error de la aplicación, de la prueba o del entorno?). El nivel más alto de la pirámide de pruebas se dibuja fino precisamente por esta estructura de costos, y proteger con pruebas de UI algo que ya se puede proteger en una capa inferior siempre sale perdiendo.
En forma de tabla de decisión queda así:
| Qué se quiere proteger | Capa adecuada | Motivo |
|---|---|---|
| Cálculos, conversiones, reglas de negocio | Pruebas unitarias | Rápidas y estables. Cubrirlas a través de la UI está fuera de discusión |
| Acceso a base de datos, E/S de archivos, integraciones externas | Pruebas de integración | Se usa lo real sin necesitar la UI (cómo trazar el límite) |
| ViewModel, lógica de presentación | Pruebas unitarias | Con MVVM se puede probar sin UI |
| Poder iniciar, que el flujo principal funcione, poder guardar | Prueba de humo de UI | Este es el campo principal de las pruebas de UI |
| Flujos críticos que en el pasado se escaparon en la verificación manual | Pruebas de UI (regresión) | Se agregan solo para los puntos donde hubo un daño real |
| Diseño de pantalla, roturas visuales | Revisión visual o comparación de capturas | Escribirlo como assert es un infierno de mantenimiento. Limite la cantidad |
| Anomalías como cierres inesperados o fugas de handles | Otra base de pruebas | Fuera del alcance de las pruebas de UI (Application Verifier) |
La forma de empezar que recomendamos es «unas 10 pruebas de humo». «Se inicia y aparece la pantalla principal», «se puede abrir el maestro principal», «se puede registrar, buscar e imprimir la vista previa de un comprobante representativo», «no aparece ningún error al salir»: se automatizan tal cual los 10 flujos principales que antes se verificaban a mano antes de cada versión. Con este alcance, escribirlas toma entre una y dos semanas, y la ejecución nocturna cae en 20 o 30 minutos, con una carga de mantenimiento razonable. Una vez que se siente el efecto, se van agregando de a una las pruebas de prevención de las regresiones que sí causaron daño real. En cambio, un plan que se plantea como objetivo «todas las pantallas, todos los campos» casi siempre termina fracasando a mitad de camino.
Otro punto importante es invertir en la dirección de extraer la lógica fuera de la UI en lugar de sumar más pruebas de UI. Una pantalla con lógica de negocio escrita directamente en los manejadores de eventos solo se puede proteger con pruebas de UI, pero si esa lógica se traslada al ViewModel o a una clase de servicio, pasa a poder protegerse con pruebas unitarias, y la prueba de UI queda reducida a «confirmar el cableado». Nuestra impresión, en la práctica, es que cuando se siente que hacen falta demasiadas pruebas de UI, casi siempre es una señal de un problema de diseño, no de las pruebas en sí. Para cómo estratificar el conjunto de pruebas, véase también «Cómo trazar el límite entre pruebas unitarias y de integración», y para la práctica de las capas inferiores, «Requisitos mínimos de un logger propio y checklist de pruebas de integración».
7. Trampas de la ejecución desatendida en CI ── sin escritorio no hay pruebas de UI
Mientras se ejecutan las pruebas de UI a mano en la máquina del desarrollador, todo está tranquilo. Las trampas se concentran en la etapa de «ejecutarlas cada noche sin supervisión en CI»: a diferencia de las pruebas de UI web (que se resuelven con un navegador headless), la prueba de una aplicación de escritorio exige una sesión de escritorio interactiva real, y de ahí surgen casi todos los problemas.
7.1 Se requiere una sesión interactiva ── un agente iniciado como servicio no funciona
Los agentes de CI (el agente de Azure Pipelines, el ejecutor autoalojado de GitHub Actions, etc.) normalmente se dejan residentes como servicio de Windows. Pero un servicio no tiene un escritorio de usuario, así que no se puede operar la ventana de una aplicación iniciada desde ahí. La propia documentación oficial de Azure Pipelines indica claramente que el agente que va a ejecutar pruebas de UI de escritorio debe configurarse no como servicio, sino como un proceso interactivo con el inicio de sesión automático (autologon) habilitado. 8 Además, los agentes hospedados por Microsoft (los ejecutores compartidos que se proveen en la nube) no admiten pruebas de UI visible; solo funcionan las pruebas con navegador headless. 8 Es decir, una prueba de UI de escritorio requiere, en la práctica, una máquina autoalojada (física o VM).
La configuración de autologon tiene un riesgo de seguridad que la propia documentación oficial anota: «cualquier persona con acceso físico a esa máquina puede usar la cuenta con la que se inició sesión automáticamente». 8 La premisa es usar una cuenta y una máquina (VM) dedicadas exclusivamente a las pruebas, sin colocar credenciales de producción.
7.2 Bloqueo de pantalla, desconexión de RDP y resolución ── las formas clásicas de fallar
Aun teniendo lista la sesión interactiva, todavía quedan trampas. Resumimos los síntomas y las medidas en una tabla.
| Trampa | Síntoma | Medida |
|---|---|---|
| El agente se inicia como servicio | No se encuentra ningún elemento, o la aplicación no arranca | Reconfigurar como proceso interactivo con autologon 8 |
| Bloqueo de pantalla o protector de pantalla | Las operaciones de entrada no llegan y la prueba falla | Deshabilitar el protector de pantalla en la configuración de autologon. Solicitar la exclusión de las GPO que inducen el bloqueo 8 |
| Desconectar el RDP con la «x» | En el instante de desconectar, la sesión queda bloqueada y todas las pruebas siguientes fallan | Volver la sesión a la consola con tscon <ID de sesión> /dest:console antes de desconectar 8 |
| Diferencias de entorno en resolución/DPI | Una prueba que pasa en local falla solo en CI | Fijar la resolución (Azure Pipelines tiene una tarea para configurarla). Unificar el escalado al 100% 8 |
| Ejecución en paralelo de pruebas | El mouse, el teclado y el foco se disputan y se destruyen mutuamente | Ejecutar las pruebas de UI en serie, una por máquina. Para paralelizar, agregar más máquinas (VM) |
| Restos de un fallo anterior | Un proceso o diálogo modal que quedó abierto impide el siguiente inicio | Limpiar los procesos objetivo antes de empezar la prueba. Ejecutar sin falta la limpieza en un finally |
La trampa del RDP es especialmente fácil de pisar, así que la ampliamos aparte. Si entra por escritorio remoto a la máquina de pruebas para ajustar algo, cierra la ventana y se retira, esa sesión queda bloqueada, y las pruebas de UI siguientes seguirán fallando. La solución que indica la documentación oficial es ejecutar, antes de desconectar, %windir%\System32\tscon.exe <ID> /dest:console desde un símbolo del sistema con permisos de administrador, para devolver la sesión a la consola. 8 Inclúyalo sin falta en el manual de operación de la máquina de pruebas.
El DPI y la resolución también merecen atención. No es raro que la VM de CI se quede en 1024×768 con el escalado predeterminado, y eso genera diferencias como «un botón que se veía en la máquina de desarrollo, en CI solo se ve haciendo scroll». Si ya eliminó el clic por coordenadas, la mayor parte de esto queda absorbido, pero de todos modos conviene fijar el entorno. El criterio de revisión de «Alto DPI en WinForms», incluido el estado de soporte de DPI de la propia aplicación, se aplica igual aquí.
7.3 Deje evidencia cuando falla ── capturas de pantalla y registros
Cuando una prueba de UI desatendida falla, si en el registro solo queda «no se encontró el elemento», no se puede saber la causa. Desde el principio, prepare el guardado de una captura de pantalla al fallar. FlaUI tiene una función para capturar la pantalla o un elemento; llámela desde el hook de fallo del framework de pruebas y guarde el resultado como artefacto de CI.
// Se llama, por ejemplo, desde el hook de fallo. Se guarda en el destino de artefactos de CI
FlaUI.Core.Capturing.Capture.Screen()
.ToFile(Path.Combine(artifactDir, $"{testName}_{DateTime.Now:HHmmss}.png"));
Además de la captura, si deja preparado el registro de la propia aplicación (hasta dónde llegó el procesamiento) y el registro del lado de la prueba (hasta qué operación tuvo éxito) para poder cruzarlos por marca de tiempo, la tarea de distinguir «¿es un error de la aplicación, de la prueba o del entorno?» se vuelve mucho más rápida. Las precauciones al programar la ejecución de pruebas por la noche con el Programador de tareas (el tipo de sesión, el problema de terminar con 0x1, etc.) están descritas en «Las tareas del Programador de tareas no se ejecutan o terminan con 0x1».
7.4 Un ejemplo mínimo de configuración de CI ── ejecutor autoalojado de GitHub Actions
Al llevar a un workflow las condiciones de las secciones 7.1 a 7.3, queda así. La premisa fundamental es que el ejecutor se configure como proceso interactivo, no como servicio de Windows, y que corra en una máquina dedicada a las pruebas con el inicio de sesión automático habilitado (sección 7.1). Si se falla en este punto, por más correcto que esté el YAML, la prueba no encontrará ni un solo elemento.
name: nightly-ui-smoke
on:
schedule:
# La especificación de cron es en UTC. Como se quiere correr a las 0:00 JST de días hábiles, en UTC es de domingo a jueves a las 15:00
- cron: "0 15 * * 0-4"
workflow_dispatch: # Se deja también la ejecución manual, para investigar
jobs:
ui-smoke:
# Etiqueta asignada al ejecutor autoalojado que corre en la sesión interactiva.
# En los ejecutores hospedados de GitHub no funciona una prueba de UI visible (sección 7.1)
runs-on: [self-hosted, windows, ui-test]
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Limpiar los restos de la ejecución anterior
shell: powershell
continue-on-error: true
run: Get-Process OrderManager -ErrorAction SilentlyContinue | Stop-Process -Force
- name: Compilar la aplicación
run: dotnet publish src/OrderManager -c Release -o artifacts/app
- name: Ejecutar las pruebas de humo de UI
run: dotnet test tests/OrderManager.UiTests -c Release --logger "trx;LogFileName=ui.trx"
- name: Recoger la evidencia
if: always() # Se necesita justo cuando falla, así que always
uses: actions/upload-artifact@v4
with:
name: ui-test-evidence
path: |
**/TestResults/**
artifacts/screenshots/**
Los cuatro puntos clave son:
runs-ondebe apuntar a la etiqueta del ejecutor autoalojado. Como se vio en la sección 7.1, los ejecutores hospedados compartidos no admiten pruebas de UI visible. 8- No olvide
timeout-minutes. Una prueba de UI atascada en un diálogo modal no vuelve por sí sola, así que hay que cortarla desde el propio job. - Escriba la limpieza como un paso. Los restos de un fallo anterior (un proceso que no se cerró) impiden el siguiente inicio (secciones 5.4 y 7.2).
- Deje evidencia con
if: always(). Si no sube como artefacto el destino de las capturas de pantalla de la sección 7.3 (en el ejemplo anterior,artifacts/screenshots) y el trx con el resultado de las pruebas, un fallo en la ejecución desatendida queda sin poder investigarse.
Cabe señalar que la necesidad de escribir el horario de schedule en UTC no es exclusiva de GitHub Actions. Escribirlo pensando en la hora de Japón y terminar con un desfase de un día es un error clásico (el manejo de fecha y hora y de la zona horaria está resumido en «Fecha, hora y zona horaria en aplicaciones empresariales»).
8. Resumen
Las pruebas de UI automatizadas no son algo que funcione con solo instalar una herramienta: solo se sostienen en el tiempo cuando se combinan la comprensión del mecanismo, la cooperación del lado de la aplicación, un límite claro de cobertura y el diseño del entorno de ejecución. Comprimimos los puntos clave.
- La base es UI Automation. Se busca en el árbol por AutomationId y se opera con los patrones de control. Primero se hace el inventario de cómo se ve la aplicación objetivo con inspect / Accessibility Insights
- La herramienta es FlaUI junto con xUnit/NUnit. WinAppDriver no tiene una versión estable desde 2020; evite adoptarlo en proyectos nuevos
- La resistencia a roturas se construye con convenciones. AutomationId asignado desde el desarrollo, clic por coordenadas prohibido, Sleep prohibido a favor de la espera condicional con Retry, y la estructura concentrada en un solo lugar con Page Object
- La cobertura empieza en unas 10 pruebas de humo. La lógica se traslada a las pruebas unitarias y de integración, y la prueba de UI se limita a confirmar que «el flujo principal funciona»
- La forma básica en CI es máquina autoalojada más sesión interactiva más autologon. Elimine con el manual de operación las trampas del bloqueo de pantalla, la desconexión de RDP y las diferencias de resolución, y deje siempre una captura de pantalla cuando falla
«Los dos días de verificación manual antes de cada versión» se pueden reemplazar, con un costo razonable, por pruebas de UI automatizadas correctamente acotadas. Por el contrario, si la aplicación actual no tiene ningún AutomationId asignado, o la lógica está escrita directamente en los eventos de pantalla, al final resulta más rápido empezar por una pequeña modificación del lado de la aplicación antes de escribir las pruebas. Podemos ayudar desde el relevamiento del estado actual de la aplicación a decidir por dónde empezar y a poner en marcha el primer paquete de pruebas de humo.
Artículos relacionados
- Cómo trazar el límite entre pruebas unitarias y de integración
- Application Verifier: una base de pruebas de casos anómalos en Windows
- Requisitos mínimos de un logger propio y checklist de pruebas de integración
- Mantenimiento de pruebas de PowerShell con Pester ── un patrón práctico para que los scripts de operación no se rompan
- Alto DPI en WinForms ── por qué se ve borroso o roto en monitores 4K y cómo abordarlo en la práctica
Áreas de consultoría relacionadas
KomuraSoft LLC trabaja en la introducción de pruebas de UI automatizadas para aplicaciones WinForms/WPF (relevamiento del estado actual, preparación de AutomationId, construcción de un paquete de pruebas de humo, diseño del entorno de CI), en la migración de activos de prueba existentes a FlaUI y en la revisión del diseño de la estrategia de pruebas en su conjunto.
- Consultoría técnica y revisión de diseño
- Desarrollo de aplicaciones Windows
- Aprovechamiento y migración de activos existentes
- Contacto
Referencias
-
Microsoft Learn, UI Automation Overview. Sobre el árbol de automatización con el escritorio como raíz, las vistas raw / de control / de contenido, las propiedades y los patrones de control de los elementos, y el hecho de que las tecnologías de asistencia y la automatización de pruebas usan la misma base. ↩ ↩2 ↩3
-
Microsoft Learn, UI Automation Control Patterns Overview. Sobre el diseño de los patrones de control (la analogía con las interfaces de un objeto COM), los patrones Invoke / Value / SelectionItem, entre otros, y el hecho de que un control puede implementar varios patrones a la vez. ↩ ↩2
-
Microsoft Learn, Accessibility tools - Inspect. Sobre que inspect.exe se incluye con el Windows SDK (
bin\<versión>\<plataforma>) y permite confirmar propiedades y patrones de UIA; sobre que está posicionada como herramienta clásica y se recomienda Accessibility Insights; sobre que la ventana se compone de una vista de árbol y una vista de datos, y sigue el foco por defecto; sobre el menú Options con UI Automation Mode / Raw View / Control View / Content View / Watch Focus / Watch Cursor / Show Highlight Rectangle / Settings; y sobre que desde el menú Action se pueden ejecutar los patrones de control del elemento seleccionado. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
GitHub, FlaUI/FlaUI. Sobre que es una envoltura de UIA compatible con Win32 / WinForms / WPF / apps de la Store; la composición de paquetes FlaUI.Core / UIA2 / UIA3; el criterio de elección entre UIA2 y UIA3 (FAQ); la licencia MIT; el lanzamiento de la v5.0.0 (febrero de 2025) y la continuidad del desarrollo; y la utilidad Retry junto con la eliminación del reintento implícito de los métodos Find a partir de la versión 2.0. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
GitHub, microsoft/WinAppDriver. Sobre que la última versión estable, v1.2.1, se publicó en noviembre de 2020; que la v1.3 sigue siendo el Release Candidate de julio de 2020 sin actualizaciones posteriores; que hay más de 1100 issues sin resolver; y que el repositorio se centra en documentación y ejemplos, sin publicar el código fuente del servidor en sí. ↩ ↩2 ↩3
-
Microsoft Learn, Use the AutomationID Property. Sobre que AutomationId es un identificador independiente de la configuración regional, que debe ser único entre los elementos hermanos, sobre su uso como clave de búsqueda en scripts de prueba, y sobre que en WPF los controles sin ID (x:Name) ni x:Uid no admiten AutomationId. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, AutomationProperties.AutomationId Attached Property. Sobre la definición de la propiedad adjunta que, en WPF (espacio de nombres System.Windows.Automation), establece la cadena que identifica de forma única a un elemento. ↩ ↩2
-
Microsoft Learn, UI testing considerations (Azure Pipelines). Sobre que las pruebas de UI de aplicaciones de escritorio requieren configurar el agente como proceso interactivo con autologon habilitado; que los agentes hospedados por Microsoft no admiten pruebas de UI visible; el bloqueo por desconexión de RDP y su solución con tscon; la tarea de configuración de resolución de pantalla; y la recolección de capturas de pantalla y video al fallar. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
GitHub, appium/appium-windows-driver. Sobre que Appium Windows Driver es una interfaz hacia el WinAppDriver que provee Microsoft, y que el propio README advierte que el servidor WinAppDriver no se mantiene desde hace mucho tiempo. ↩ ↩2
-
Microsoft Learn, Use Coded UI tests to test your code. Sobre que las pruebas Coded UI están obsoletas y que Visual Studio 2019 es la última versión totalmente compatible, y sobre que como migración se recomendaba Appium más WinAppDriver para aplicaciones de escritorio / UWP (la eliminación en VS 2026 se documenta en la misma guía de migración de Learn). ↩ ↩2
-
Microsoft Learn, WinForms: Setting the accessible name on a control. Sobre que la propiedad Name de UIA de un control de WinForms reutiliza, en muchos tipos, la propiedad Text, y sobre que en TextBox, ListBox y similares hace falta configurar explícitamente AccessibleName. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Práctica de CI/CD para aplicaciones WinForms / WPF — automatizar desde la compilación hasta la firma y la distribución con GitHub Actions
CI/CD para WinForms/WPF con GitHub Actions: build y pruebas, versión por tags, firma con signtool y tabla de decisión por formato de dist...
Integrar la autenticación de Entra ID en aplicaciones WinForms/WPF — Configuración práctica con MSAL.NET y el bróker WAM
Cómo integrar Entra ID en apps WinForms/WPF: cliente público, registro de la app, AcquireTokenSilent, bróker WAM y persistencia de la cac...
Introducción a la accesibilidad en aplicaciones Windows — UI Automation y cómo prepararse para la obligatoriedad del ajuste razonable
Ante la reforma legal japonesa vigente desde abril de 2024, explicamos UI Automation, el mecanismo con el que los lectores de pantalla le...
Iconos de la bandeja del sistema y notificaciones toast en aplicaciones Windows — los escollos de NotifyIcon y cómo elegir el AppNotification adecuado
Organiza la implementación de la residencia en la bandeja del sistema y las notificaciones toast en aplicaciones Windows empresariales: e...
Fecha, hora y zona horaria en aplicaciones empresariales — de las trampas de DateTime al principio de UTC y el diseño de pruebas
Un traslado de servidor desplaza la hora 9 horas: exploramos el origen en el Kind de DateTime, DateTimeOffset, el principio de UTC, TimeZ...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Hilo de UI y temporizadores
Hilo de UI de WPF / WinForms, flujos asíncronos, Dispatcher y diseño de temporizadores.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Qué debería usarse para las pruebas de UI automatizadas de una aplicación WinForms/WPF?
- Nuestra recomendación es FlaUI junto con xUnit/NUnit. FlaUI es un software OSS con licencia MIT que envuelve de forma ligera Windows UI Automation (UIA), es compatible tanto con UIA2 como con UIA3, y se mantiene activamente: por ejemplo, la versión v5.0.0 se publicó en febrero de 2025. Como criterio general, conviene probar primero UIA3 para WPF y UIA2 para WinForms. Como no incluye un framework de pruebas propio, se puede integrar en el mismo ejecutor de pruebas y en el mismo pipeline de CI que las pruebas unitarias. El primer paso, antes de escribir código, es confirmar con inspect.exe o Accessibility Insights for Windows cómo se ven expuestos los elementos de la aplicación objetivo.
- ¿Conviene adoptar WinAppDriver hoy en día?
- No recomendamos adoptarlo en proyectos nuevos. WinAppDriver, de Microsoft, tiene su última versión estable, v1.2.1, detenida desde noviembre de 2020; la v1.3 sigue siendo un Release Candidate de julio de 2020 y acumula más de 1100 issues sin resolver. Además, en el repositorio de GitHub solo hay documentación y ejemplos: el código fuente del servidor en sí no es público, por lo que la comunidad tampoco puede corregir sus errores. El Windows Driver de Appium usa WinAppDriver internamente, así que hereda las mismas limitaciones. Si ya cuenta con activos de prueba existentes no hace falta descartarlos de inmediato, pero recomendamos orientar las pruebas de los nuevos flujos hacia FlaUI.
- ¿Cómo se evita que las pruebas de UI automatizadas sean frágiles?
- El 80% de la resistencia a roturas se decide con la convención de asignar AutomationId desde el desarrollo. En WPF, el identificador es x:Name o AutomationProperties.AutomationId; en WinForms, es Control.Name. A partir de ahí, se prohíbe la búsqueda basada en el texto visible (Name) y el clic por coordenadas, se usa la espera condicional de la clase Retry de FlaUI en lugar de Thread.Sleep, y se concentra la estructura en un solo lugar mediante el patrón Page Object, que encierra la búsqueda y la operación de elementos por cada pantalla. Como FlaUI eliminó a partir de la versión 2.0 el reintento implícito de los métodos Find, conviene establecer como norma envolver siempre en Retry la búsqueda de elementos que aparecen de forma asíncrona.
- ¿Hasta qué punto conviene escribir pruebas de UI automatizadas?
- Recomendamos empezar con unas 10 pruebas de humo. Las pruebas de UI son órdenes de magnitud más lentas y más frágiles que las pruebas unitarias, así que los cálculos y las reglas de negocio se protegen con pruebas unitarias, el acceso a la base de datos con pruebas de integración, y las pruebas de UI se limitan a confirmar que «la aplicación arranca y los flujos principales funcionan». Si automatiza los 10 flujos principales que antes verificaba a mano antes de cada versión, escribirlas toma entre una y dos semanas, y la ejecución nocturna se mantiene en 20 o 30 minutos. En cambio, un plan que apunta a cubrir «todas las pantallas y todos los campos» casi siempre termina fracasando. Si siente que necesita demasiadas pruebas de UI, suele ser una señal de que conviene mejorar el diseño extrayendo la lógica hacia el ViewModel para poder protegerla con pruebas unitarias.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.