Blog
Testowanie24 min czytania

Maestro — testy UI aplikacji mobilnych i webowych opisane w YAML

Jeden framework E2E na Androida, iOS i przeglądarkę, w którym test to plik YAML bez kompilacji: pięćdziesiąt komend, selektory po widocznym tekście i automatyczne czekanie zamiast sleepów. Pokazujemy wdrożenie webowe pod aplikację Laravela — od pierwszego flow, przez zasilanie danych przez HTTP z poziomu testu, po raport JUnit w GitHub Actions bez płatnej chmury. Plus rachunek za wersję beta i licencyjny podział na Apache 2.0, darmowe zamknięte Studio i chmurę po 250 USD za urządzenie.

Testy end-to-end mają jedną wspólną cechę niezależnie od stacku: zespół pisze je z zapałem przez dwa tygodnie, a potem przez rok szuka wymówek, żeby ich nie naprawiać. Powód jest zwykle ten sam — koszt utrzymania. Selektory rozjeżdżają się po każdym refaktorze, testy migają losowo na CI, a napisanie nowego scenariusza wymaga znajomości frameworka, który ma własny model asynchroniczności, własny runner i własny sposób na czekanie.

Maestro (github.com/mobile-dev-inc/Maestro, licencja Apache 2.0) atakuje ten problem od strony formatu: test to plik YAML, który da się przeczytać na głos. Framework powstał z myślą o aplikacjach mobilnych — Android, iOS, React Native, Flutter — ale od kilku wydań obsługuje też przeglądarkę, i to jest część, która interesuje nas najbardziej przy aplikacjach webowych budowanych w Laravelu.

Poniżej: co ten format realnie potrafi, gdzie kończy się jego wygoda, jak wpiąć go w pipeline aplikacji webowej bez płacenia za chmurę producenta i co mówi licencja, kiedy chce się tego użyć w projekcie komercyjnym.

Co to jest i na czym stoi

Maestro nazywa swoje podejście „arm's length” — na wyciągnięcie ramienia. Framework nie wstrzykuje się w kod aplikacji i nie manipuluje DOM-em ani hierarchią widoków od środka. Steruje urządzeniem albo przeglądarką z zewnątrz, a to, co widzi, czyta z drzewa dostępności — tej samej struktury, z której korzystają czytniki ekranu.

Konsekwencje tej decyzji są odczuwalne w codziennej pracy:

  • test jest niezależny od technologii wnętrza — natywny Android, SwiftUI, React Native, Flutter i zwykły HTML wyglądają dla Maestro tak samo, bo wszystkie kończą się drzewem dostępności,
  • da się dotknąć rzeczy spoza aplikacji: systemowego okna uprawnień, ustawień, powiadomienia, okna wyboru zdjęcia,
  • elementy, które są niewidoczne dla technologii asystujących, są niewidoczne również dla testu. To bywa upierdliwe, ale też jest darmowym audytem dostępności — jeśli Maestro nie widzi przycisku, czytnik ekranu też go nie widzi.

Drugą decyzją projektową jest interpretowany YAML zamiast kompilowanego kodu. Flow nie przechodzi przez Gradle'a ani przez tsc. Zapisujesz plik, odpalasz komendę, patrzysz na urządzenie. Pętla zwrotna liczy się w sekundach, nie w minutach — i to jest różnica, która decyduje o tym, czy ktoś w zespole faktycznie napisze test, czy odłoży go na „po sprincie”.

Stan repozytorium na 4 września 2026, prosto z API GitHuba:

  • 15 506 gwiazdek, 941 forków, 117 kontrybutorów, repozytorium założone 31 marca 2022,
  • silnik napisany w Kotlinie, wydawany jako CLI na JVM,
  • 20 commitów w ostatnich trzydziestu dniach, ostatnie wydanie cli-2.10.0 z 31 sierpnia 2026, poprzednie 2.9.0 z 26 sierpnia — kadencja to mniej więcej wydanie na dwa–trzy tygodnie,
  • 395 otwartych zgłoszeń i 107 otwartych pull requestów — do tej liczby wracamy w sekcji o pułapkach,
  • licencja Apache 2.0.

Pierwszy flow

Instalacja wymaga Javy w wersji 17 lub nowszej i zmiennej JAVA_HOME wskazującej na tę instalację. Sam CLI wchodzi jednym poleceniem:

curl -fsSL "https://get.maestro.mobile.dev" | bash

Na macOS działa też Homebrew:

brew tap mobile-dev-inc/tap
brew trust --formula mobile-dev-inc/tap/maestro
brew install mobile-dev-inc/tap/maestro

Na Windowsie instaluje się z paczki maestro.zip ze strony wydań i dopisuje katalog bin do PATH. Wariant przez WSL istnieje, ale dokumentacja sama odradza go poza sytuacjami, w których nie ma wyjścia — wymaga ręcznego przekierowania portów do emulatora działającego po stronie Windowsa.

Test wygląda tak (przykład z README projektu):

# flow_contacts_android.yaml

appId: com.android.contacts
---
- launchApp
- tapOn: "Create new contact"
- tapOn: "First Name"
- inputText: "John"
- tapOn: "Last Name"
- inputText: "Snow"
- tapOn: "Save"

Nad separatorem --- siedzi konfiguracja flow, pod nim lista komend wykonywanych po kolei. Uruchomienie to maestro test flow_contacts_android.yaml. Nie ma tu klas, dekoratorów, konfiguracji runnera ani pliku, w którym trzeba zarejestrować nowy scenariusz.

Największą zaletą tego formatu nie jest to, że pisze się go szybko. Jest nią to, że osoba nietechniczna potrafi przeczytać taki plik i powiedzieć, czy scenariusz zgadza się z tym, co uzgodniono na spotkaniu.

Przeglądarka: część, która dotyczy aplikacji webowych

Wsparcie dla przeglądarki jest oznaczone jako beta i ogranicza się do Chromium. Różnica w składni sprowadza się do jednego klucza — zamiast appId podaje się url:

# example.yaml
url: https://maestro.mobile.dev
---
- launchApp
- tapOn: "Installing Maestro"
- assertVisible: "Installing the CLI"

Przy pierwszym uruchomieniu Maestro pobiera własną, zarządzaną wersję Chromium — nie korzysta z przeglądarki zainstalowanej w systemie, więc wersja silnika jest taka sama na laptopie programisty i na maszynie CI. Kolejne przebiegi startują od razu.

Dla aplikacji w Laravelu z Inertią wygląda to tak jak każdy inny scenariusz logowania:

url: http://localhost:8000/login
tags:
  - smoke
  - auth
---
- launchApp:
    clearState: true
- tapOn: "E-mail"
- inputText: ${EMAIL}
- tapOn: "Hasło"
- inputText: ${PASSWORD}
- tapOn: "Zaloguj się"
- assertVisible: "Panel klienta"
- assertNotVisible: "Nieprawidłowe dane logowania"

Stan przeglądarki — ciasteczka, localStorage — jest domyślnie zachowywany między flow w obrębie jednego przebiegu. To wygodne, kiedy kilka scenariuszy dzieli sesję, i groźne, kiedy jeden test zostawia po sobie zalogowanego użytkownika, a następny zakłada, że jest wylogowany. Czyści się to per origin, komendą clearState albo opcją clearState na launchApp, tak jak wyżej.

Ograniczenia bety, o których trzeba wiedzieć od razu

  • tylko Chromium — nie ma Firefoksa ani WebKita, więc test cross-browser to nadal zadanie dla innego narzędzia,
  • locale ustawione na sztywno na en-US. Jeśli aplikacja wybiera język po nagłówku Accept-Language, w teście dostaniesz angielski — trzeba wymusić język ścieżką, parametrem albo ciasteczkiem,
  • rozmiar okna jest z góry ustawiony w trybie z widoczną przeglądarką. W trybie bezgłowym da się go podać flagą --screen-size=1920x1080, co w praktyce oznacza, że responsywność testuje się w headless albo wcale.

Do CI używa się flagi --headless. Reszta komend zachowuje się identycznie jak na urządzeniu mobilnym — to jest cały sens tego projektu.

Selektory: najpierw tekst, potem identyfikator

Selektor w najkrótszej formie to zwykły napis:

- tapOn: Zaloguj się   # równoważne { text: "Zaloguj się" }

Ważny szczegół: pola text i idtraktowane jak wyrażenia regularne. Napis „Koszyk (3)” trzeba więc dopasować wzorcem Koszyk \(.*\), a nie literalnie — nawias jest znakiem specjalnym. Z drugiej strony ta sama cecha rozwiązuje problem dynamicznych treści bez żadnej dodatkowej składni.

Kiedy sam tekst nie wystarcza, selektor rozwija się do bloku, w którym wszystkie warunki muszą być spełnione jednocześnie:

- tapOn:
    id: submit_button
    enabled: true
    below: Hasło

Dostępne rodziny selektorów:

  • podstawowetext, id, index, point oraz css dostępne wyłącznie w przeglądarce,
  • relacyjneabove, below, leftOf, rightOf, containsChild, childOf; pozwalają zaczepić się o stabilny nagłówek sekcji i wskazać element względem niego,
  • stanuenabled, checked, focused, selected,
  • cech — filtrowanie po fizycznych właściwościach elementu, na przykład square albo long-text,
  • wymiarówwidth i height z parametrem tolerance.

Rekomendacja producenta brzmi: tekst tam, gdzie się da, identyfikator tam, gdzie trzeba. Selektor po widocznym tekście jest samodokumentujący i sprawdza to, co widzi użytkownik — jeśli tekst się zmienił i test padł, to najczęściej znaczy, że interfejs faktycznie się zmienił. Identyfikatory zostawia się dla ikon, obrazków i aplikacji wielojęzycznych, gdzie tekst zmienia się razem z locale.

Warto zapamiętać jeszcze jedno: dopisanie enabled: true do selektora przycisku, który aktywuje się po walidacji formularza albo po odpowiedzi z API, sprawia, że Maestro poczeka na aktywność przycisku, zamiast kliknąć w martwy element. To jedna linijka, która usuwa całą klasę migających testów.

Czekanie bez sleepów

Asercje w Maestro nie sprawdzają stanu raz. Odpytują interfejs w pętli, aż warunek zostanie spełniony albo skończy się limit czasu. W praktyce oznacza to, że assertVisible jest jednocześnie asercją i komendą czekania:

- tapOn: "Zapisz"
- assertVisible: "Zmiany zapisane"

Analogicznie assertNotVisible czeka na zniknięcie spinnera albo zamknięcie modala. Do sytuacji, których nie da się wyrazić asercją, są komendy extendedWaitUntil (z jawnym timeout) i waitForAnimationToEnd, a do niestabilnych fragmentów — retry, który powtarza blok komend zadaną liczbę razy.

To jest najbardziej niedoceniana część tego frameworka. Większość migotania w testach E2E bierze się z ręcznych sleep(2), które na szybkiej maszynie są za długie, a na obciążonym CI za krótkie. Tutaj takiej komendy się po prostu nie używa.

Pełny zestaw komend

Lista komend jest zamknięta i widać ją wprost w kodzie źródłowym (YamlFluentCommand.kt). Około pięćdziesięciu pozycji, pogrupowanych:

  • interakcjetapOn, doubleTapOn, longPressOn, swipe, scroll, scrollUntilVisible, back, pressKey, hideKeyboard,
  • wprowadzanie tekstuinputText, eraseText, pasteText, copyTextFrom, setClipboard oraz rodzina inputRandomText, inputRandomNumber, inputRandomEmail, inputRandomPersonName, inputRandomCityName, inputRandomCountryName, inputRandomColorName,
  • asercjeassertVisible, assertNotVisible, assertTrue, assertScreenshot, assertDarkMode, assertLightMode plus komendy oparte o model językowy,
  • cykl życia aplikacjilaunchApp, stopApp, killApp, clearState, clearKeychain, openLink, openBrowser, setPermissions,
  • środowisko urządzeniasetLocation, travel (symulacja ruchu po trasie), setOrientation, setAirplaneMode, toggleAirplaneMode, setDarkMode, toggleDarkMode, addMedia,
  • sterowanie przebiegiemrunFlow, repeat, retry, runScript, evalScript, extendedWaitUntil, waitForAnimationToEnd,
  • artefaktytakeScreenshot, startRecording, stopRecording.

Komenda setDarkMode i asercje assertDarkMode/assertLightMode to nowość z wydania 2.9.0 z sierpnia 2026 — przełączanie motywu z poziomu testu przestało wymagać grzebania w ustawieniach systemowych. Przy aplikacjach, które mają tryb ciemny (a mają go już praktycznie wszystkie), to oszczędza osobnego subflow na każdą platformę.

Logika: warunki, pętle, subflow i hooki

YAML nie ma instrukcji sterujących, więc Maestro dokłada je jako parametry komend. Warunek to blok when obsługujący cztery predykaty: visible, notVisible, platform (Android, iOS, Web) i true z wyrażeniem JavaScriptu.

- runFlow:
    when:
      visible: "Akceptuję cookies"
    commands:
      - tapOn: "Akceptuję cookies"

- runFlow:
    when:
      platform: Web
    file: subflows/web-permissions.yaml

Powtarzalne fragmenty wynosi się do osobnych plików i wywołuje przez runFlow, przekazując zmienne w bloku env. Do rzeczy, które mają się dziać przy każdym teście, są hooki deklarowane w sekcji konfiguracyjnej:

url: http://localhost:8000
onFlowStart:
  runFlow:
    file: subflows/login.yaml
    env:
      ROLE: "admin"
onFlowComplete:
  - runScript: teardown.js
---
- launchApp

Semantyka błędów w hookach jest opisana wprost i zgadza się z tym, co robi JUnit: jeśli padnie onFlowStart, całe flow jest oznaczone jako nieudane, jego treść jest pomijana, ale onFlowComplete i tak się wykona, żeby posprzątać. Jeśli padnie onFlowComplete, flow jest nieudane nawet wtedy, gdy właściwy test przeszedł.

Jedno ostrzeżenie z dokumentacji, które łatwo zignorować: hook wykonuje się przed każdym flow. Logowanie przez interfejs w onFlowStart przy pięćdziesięciu scenariuszach to pięćdziesiąt razy przeklikany formularz. Szybciej jest zalogować się przez API — o czym niżej.

JavaScript i HTTP: zasilanie danych bez klikania

Do flow można wstrzyknąć JavaScript — pojedynczym wyrażeniem (evalScript) albo plikiem (runScript). Silnikiem jest GraalJS (poprzedni, Rhino, wyleciał w wydaniu 2.6.0), a skrypt działa w ograniczonym środowisku bez dostępu do systemu plików.

Najciekawszy jest wbudowany klient HTTP — nakładka na okhttp3 z metodami http.get, http.post, http.put, http.delete i ogólnym http.request. Wyniki przekazuje się do flow przez globalny obiekt output:

// seed_order.js
const response = http.post('http://localhost:8000/api/testing/orders', {
    headers: {
        'Authorization': 'Bearer ' + MAESTRO_TOKEN,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status: 'paid', total: 149.99 })
});

const data = json(response.body);

output.orderNumber = data.number;
# flow.yaml
url: http://localhost:8000/zamowienia
env:
  MAESTRO_TOKEN: ${MAESTRO_TOKEN}
---
- runScript: seed_order.js
- launchApp
- assertVisible: ${output.orderNumber}

Dla zespołu pracującego w Laravelu to jest najbardziej praktyczna część całego frameworka. Zamiast klikać przez trzy ekrany, żeby doprowadzić aplikację do stanu „użytkownik ma opłacone zamówienie”, wystawia się wąską trasę testową i woła ją z poziomu flow. Trasa może stać za middlewarem odcinającym ją poza środowiskiem lokalnym:

// routes/testing.php — dołączane tylko poza produkcją
Route::middleware('auth:sanctum')
    ->prefix('api/testing')
    ->group(function (): void {
        Route::post('orders', CreateTestOrderController::class);
    });
// bootstrap/app.php
->withRouting(
    then: function (): void {
        if (! app()->isProduction()) {
            Route::group([], base_path('routes/testing.php'));
        }
    },
)

Obiekt response zwracany przez klienta HTTP ma pola ok, status, body i headers, więc odpowiedź da się sprawdzić przed przejściem do interfejsu. Jest też wbudowany DataFaker dostępny jako obiekt faker — do generowania adresów e-mail i nazwisk pod formularze rejestracji z unikalnymi polami.

Ograniczenie, o którym warto pamiętać: to nie jest miejsce na logikę biznesową testu. Skrypty mają przygotować dane i zapisać wynik do output, a nie sterować przebiegiem — inaczej po pół roku okaże się, że flow to trzy linijki YAML-a wołające tysiąc linii JavaScriptu i cała czytelność, dla której się ten framework wybrało, poszła się paść.

Organizacja repozytorium i tagi

Domyślnie Maestro wykonuje wyłącznie pliki leżące bezpośrednio w podanym katalogu i ignoruje podkatalogi. Żeby zorganizować testy w foldery, deklaruje się wzorce w config.yaml:

# config.yaml
url: http://localhost:8000
testOutputDir: build/maestro-results
flows:
  - "**"
  - "!helpers/**"
  - "!wip.yaml"

Wzorce negatywne z przedrostkiem ! to nowość z wydania 2.9.0. Przydają się na pliki pomocnicze, które przypadkiem mają rozszerzenie .yaml i wcześniej trzeba je było trzymać poza katalogiem testów. Lista musi zawierać co najmniej jeden wzorzec pozytywny — sama negacja jest odrzucana z błędem walidacji.

Tagi deklaruje się w nagłówku flow i filtruje przy uruchomieniu:

maestro test . --include-tags=smoke --exclude-tags=wip

Logika jest tu nieoczywista i warto ją znać, zanim ktoś zbuduje na niej pipeline: w obrębie jednej flagi tagi łączy alternatywa (--include-tags "auth,checkout" uruchomi testy otagowane auth lub checkout), a między flagami koniunkcja — najpierw wybierane są flow z tagami z listy włączeń, potem z tego zbioru usuwane są te z tagami z listy wyłączeń. Uruchomienia „tylko testy oznaczone jednocześnie smoke i auth” nie da się dziś wyrazić.

Raporty i CI bez płatnej chmury

Raporty generuje się flagą --format. Dostępne są junit, html oraz html-detailed (z krokami), a miejsce zapisu wskazuje --output:

maestro test --headless \
  --format junit --output build/report.xml \
  --test-output-dir build/maestro-results \
  ./e2e

Do raportu JUnit można dołożyć własne metadane, deklarując je w nagłówku flow — trafiają do XML-a jako elementy <property> przy <testcase>, co pozwala powiązać scenariusz z identyfikatorem w systemie zarządzania testami:

url: http://localhost:8000
name: Logowanie klienta
properties:
    testCaseId: "TC-101"
    priority: "High"
---
- launchApp

Artefakty przebiegu przeszły przebudowę w wydaniu 2.7.0: zamiast zagnieżdżonych katalogów jest płaska paczka z ustrukturyzowanym manifestem, czytelnymi nazwami kroków, logami urządzenia i raportami awarii. Do tego zrzut ekranu przed każdym krokiem, a przy błędzie dodatkowo hierarchia widoków. Przy diagnozie testu, który padł raz na CI o trzeciej w nocy, to jest różnica między „wiemy, co się stało” a „spróbujmy odtworzyć lokalnie”.

GitHub Actions

Tu jest szczegół, który łatwo przeoczyć czytając dokumentację: oficjalna akcja mobile-dev-inc/action-maestro-cloud wymaga płatnego planu Maestro Cloud. Wysyła binarkę aplikacji i flow do chmury producenta i tam je uruchamia.

Nic nie stoi jednak na przeszkodzie, żeby uruchomić CLI lokalnie na runnerze — i dla testów webowych aplikacji w Laravelu to jest wariant domyślny, bo nie ma tu żadnej binarki do wgrywania na urządzenie:

name: E2E

on: [push, pull_request]

jobs:
  maestro-web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
      - run: composer install --no-interaction --prefer-dist
      - run: npm ci && npm run build
      - run: cp .env.example .env && php artisan key:generate
      - run: php artisan migrate --seed --force
      - run: php artisan serve --port=8000 &
      - run: curl -fsSL "https://get.maestro.mobile.dev" | bash
      - run: |
          export PATH="$PATH:$HOME/.maestro/bin"
          maestro test --headless \
            --screen-size=1920x1080 \
            --format junit --output build/report.xml \
            ./e2e
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: maestro-results
          path: |
            build/report.xml
            build/maestro-results

Do rozdzielania dużych zestawów są flagi --shards, --shard-all i --shard-split. Pierwsza dzieli zestaw na równoległe fragmenty, druga uruchamia wszystkie testy na każdym z podłączonych urządzeń, trzecia rozdziela je równo między urządzenia.

Regresja wizualna

Komenda assertScreenshot porównuje bieżący ekran z zapisanym wzorcem:

- assertScreenshot:
    path: koszyk.png
    cropOn:
      id: summary
    thresholdPercentage: 98

Domyślny próg zgodności to 95 procent, a parametr cropOn pozwala zawęzić porównanie do jednego elementu — co jest jedynym sensownym sposobem użycia tej komendy w aplikacji, która ma na ekranie datę, licznik albo cokolwiek innego zmieniającego się między przebiegami. Próg da się wstawić ze zmiennej, więc może być inny lokalnie i inny na CI. Jeśli wyrażenie nie rozwinie się do liczby (na przykład zmienna jest nieustawiona i daje pusty napis), asercja kończy się błędem Invalid thresholdPercentage zamiast po cichu wrócić do wartości domyślnej — i dobrze.

Funkcje oparte o model językowy

Maestro ma zestaw komend korzystających z LLM-a: assertWithAI (asercja opisana zdaniem w języku naturalnym), assertNoDefectsWithAI (audyt zrzutu ekranu pod kątem ucięć, nachodzących elementów i błędów układu), extractTextWithAI oraz flagę --analyze, która po przebiegu generuje raport HTML z uwagami o literówkach, gramatyce i problemach z lokalizacją.

Trzy rzeczy do policzenia przed włączeniem tego w projekcie klienckim:

  • Wymaga konta w Maestro Cloud. Darmowe wystarczy — same komendy AI nie potrzebują płatnego planu — ale trzeba się zalogować (maestro login) albo ustawić MAESTRO_CLOUD_API_KEY. Nie da się podstawić własnego klucza do OpenAI czy Anthropica; zmienne MAESTRO_CLI_AI_KEY i MAESTRO_CLI_AI_MODEL zostały wycofane, a model wybiera producent.
  • Zrzuty ekranu wychodzą poza twoją infrastrukturę. Przy aplikacji z danymi osobowymi albo pod NDA to jest decyzja do podjęcia świadomie i najlepiej na piśmie, a nie flaga dopisana do polecenia w piątek po południu.
  • assertNoDefectsWithAI ma domyślnie optional: true, czyli nie wywraca testu. To celowe — chodzi o to, żeby zmienna odpowiedź modelu nie blokowała pipeline'u. Trzeba o tym pamiętać, zanim ktoś uzna, że skoro CI świeci na zielono, to audyt wizualny przeszedł.

Serwer MCP: pisanie testów przez agenta

CLI zawiera serwer Model Context Protocol uruchamiany komendą maestro mcp. Dla Claude Code podpina się go jedną linijką:

claude mcp add maestro -- maestro mcp

Po stronie Claude Desktop trzeba wpisać pełną ścieżkę do binarki i jawnie podać JAVA_HOME w claude_desktop_config.json — aplikacja startuje z minimalnej powłoki i nie widzi zmiennych z profilu użytkownika.

Serwer wystawia dziewięć narzędzi: list_devices, inspect_screen (hierarchia widoków jako zwięzły JSON), take_screenshot, run (wykonanie flow — z pliku, katalogu albo wprost z YAML-a w treści wywołania), cheat_sheet, open_maestro_viewer oraz trzy chmurowe: list_cloud_devices, run_on_cloud, get_cloud_run_status.

Ta konstrukcja jest sensowniejsza, niż brzmi. Agent, który pisze test E2E bez dostępu do urządzenia, zmyśla selektory. Agent, który może zawołać inspect_screen, dostać hierarchię, napisać flow, uruchomić je przez run i zobaczyć błąd, pracuje na faktach. W wydaniu 2.5.0 producent świadomie skrócił listę narzędzi z piętnastu do ośmiu, usuwając pojedyncze akcje w rodzaju tap_on czy input_text na rzecz jednego run przyjmującego YAML — bo agent i tak ma pisać flow, a nie klikać przez protokół.

Poprzednik tego rozwiązania, komenda maestro chat (MaestroGPT), został wycofany w wydaniu 2.7.0.

Studio, Viewer i co jest zamknięte

Ekosystem wokół CLI składa się z kilku elementów o różnym statusie licencyjnym i warto je rozdzielić:

  • Maestro CLI — silnik wykonujący flow. Otwarty, Apache 2.0, to jest to repozytorium,
  • Maestro Studio — desktopowe IDE do budowania testów klikaniem, z inspektorem elementów i nagrywaniem interakcji. Darmowe, ale nie open source — kodu nie ma w repozytorium, co README stwierdza wprost. W wydaniu 2.6.0 Studio zostało wyjęte z CLI na rzecz osobnej aplikacji desktopowej,
  • Maestro Viewer — dodany w 2.6.0 podgląd hierarchii widoków i komend na żywo,
  • Maestro Cloud — płatna infrastruktura do równoległych przebiegów na hostowanych urządzeniach. Cennik podaje 250 USD miesięcznie za urządzenie, rozliczane od liczby równoległych wykonań; jest też plan Enterprise z indywidualną wyceną. Obowiązuje miękki limit 20 minut na wykonanie — dłuższy przebieg może zostać przerwany.

Model jest więc klasyczny dla projektów firmowych: silnik otwarty, wygoda i skala płatne. Nic w tym złego, ale przy planowaniu wdrożenia trzeba wiedzieć, która część zniknie, jeśli budżet się skończy. Odpowiedź brzmi: CLI zostaje w całości i to on wykonuje testy — traci się chmurę, oficjalną akcję GitHuba i komendy oparte o model językowy.

Pułapki

  • Java 17 w każdym środowisku. Dla zespołu PHP to dodatkowa zależność w obrazie CI i na maszynie każdego programisty. Nie ma oficjalnego obrazu Dockera — dokumentacja pokazuje konfiguracje CircleCI oparte o gotowe obrazy androidowe, ale własny obraz z Javą i CLI trzeba złożyć samodzielnie.
  • iOS wymaga macOS z Xcode i narzędziami wiersza poleceń. Na runnerach linuksowych da się uruchomić testy Androida i przeglądarki, iOS-a nie.
  • 395 otwartych zgłoszeń i 107 pull requestów. Liczba jest wysoka jak na 117 kontrybutorów. Projekt ma automatyzację zamykającą zgłoszenia bez odpowiedzi zgłaszającego (żywcem wzorowaną na tej z Fluttera), więc część tego to zgłoszenia w stanie oczekiwania na reprodukcję — ale sto otwartych pull requestów oznacza też, że poprawka od społeczności może poczekać.
  • Beta w przeglądarce to naprawdę beta. Zmiany w changelogu z ostatnich wydań — „napraw zepsute testy na Chrome 150+”, „ponawiaj przejściowe błędy JavaScriptu”, „nie pozwól, żeby zepsuty rejestrator ekranu zepsuł pobieranie hierarchii” — mówią, w którym miejscu ta ścieżka jest jeszcze świeża.
  • Regexy w selektorach zaskakują. Nawiasy, kropki i znaki zapytania w widocznym tekście trzeba escapować. Test, który przechodzi na „Zapisz”, ale nie na „Zapisz (2 zmiany)”, to zwykle właśnie to.
  • Stan przeglądarki przenosi się między flow. Jeśli scenariusze mają być niezależne, clearState musi być świadomą decyzją, a nie przypadkiem.

Czy warto, jeśli robi się głównie web

Uczciwa odpowiedź brzmi: zależy, czy w projekcie jest też aplikacja mobilna.

Jeśli produkt to wyłącznie aplikacja webowa, dojrzalsze narzędzia — Playwright albo Laravel Dusk, który jest bliżej stacku — dadzą dziś więcej: wiele silników przeglądarek, kontrolę nad viewportem także w trybie z widoczną przeglądarką, przechwytywanie żądań sieciowych, dowolne locale i ekosystem, który nie jest w becie. Maestro webowe nadrabia czytelnością zapisu, ale nie pokrywa tego zakresu.

Jeśli natomiast produkt ma aplikację mobilną i panel webowy — a to jest częsty układ w projektach, które prowadzimy — rachunek się zmienia. Jeden format zapisu testów dla wszystkich trzech platform, jeden zestaw komend do nauczenia, jeden raport JUnit w pipelinie. Alternatywą jest utrzymywanie Playwrighta do weba i osobnego frameworka do mobile, z dwoma zestawami konwencji i dwoma zespołami, które nie potrafią czytać nawzajem swoich testów.

Drugi scenariusz, w którym Maestro wygrywa niezależnie od stacku: testy pisane przez osoby, które nie programują. Format YAML z komendami w rodzaju „kliknij X, sprawdź, że widać Y” jest realnie dostępny dla testera manualnego, analityka albo osoby odpowiedzialnej za produkt. Próg wejścia dla Playwrighta z TypeScriptem jest tu zauważalnie wyższy.

Czy projekt żyje

Żyje i ma tempo produktu z zespołem na etacie, nie hobbystycznego repozytorium. Dwadzieścia commitów w ostatnim miesiącu, wydania 2.9.0 i 2.10.0 w odstępie pięciu dni pod koniec sierpnia 2026, changelog rozbity na sekcje Core / Android / iOS / Web / CLI, podziękowania dla kontrybutorów przy każdym wydaniu.

Charakter zmian też sporo mówi. Ostatnie wydania to głównie łatanie konkretnych przypadków brzegowych: hierarchia WebView na Androidzie przy cyklicznych drzewach DOM w Reakcie i Vue, obsługa awarii natywnych (SIGSEGV), a nie tylko wyjątków JVM, ekran główny iPada na iOS 26, dowiązanie raportów JUnit i HTML do przebiegów w chmurze. To profil projektu, który jest w produkcji u kogoś, kto zgłasza takie rzeczy z prawdziwego pipeline'u.

Podsumowanie

Maestro jest wart uwagi wtedy, gdy zależy ci na tym, żeby test E2E dało się przeczytać i poprawić bez wchodzenia w kod frameworka — a szczególnie wtedy, gdy ten sam produkt ma wersję mobilną i webową.

  • Test to plik YAML — bez kompilacji, bez runnera do skonfigurowania, z pętlą zwrotną liczoną w sekundach,
  • około pięćdziesięciu komend pokrywających interakcje, wprowadzanie tekstu, asercje, cykl życia aplikacji, środowisko urządzenia i artefakty,
  • czekanie wbudowane w asercje — nie ma komendy sleep, bo nie jest potrzebna,
  • selektory po widocznym tekście, z regexami w standardzie i relacyjnym zaczepianiem o stabilne elementy,
  • wbudowany klient HTTP w JavaScripcie — dane testowe przygotowuje się przez API zamiast klikać przez trzy ekrany,
  • raporty JUnit, HTML i html-detailed plus paczka artefaktów ze zrzutem przed każdym krokiem i hierarchią widoków przy błędzie,
  • przeglądarka obsługiwana w becie — Chromium pobierany i zarządzany przez Maestro, tryb bezgłowy z konfigurowalnym rozmiarem okna, ale locale zablokowane na en-US,
  • serwer MCP w komplecie, dzięki czemu agent pisze testy z podglądem prawdziwej hierarchii, zamiast zgadywać selektory,
  • projekt aktywny — 15 506 gwiazdek, 117 kontrybutorów, wydanie 2.10.0 z 31 sierpnia 2026.

Do rozstrzygnięcia przed wdrożeniem: Java 17 w obrazie CI (nie ma oficjalnego obrazu Dockera), testy iOS wyłącznie na macOS oraz świadomość, że wsparcie dla przeglądarki jest w becie i nie zastąpi Playwrighta tam, gdzie potrzebne są różne silniki, różne locale albo przechwytywanie ruchu sieciowego.

Licencja: silnik i CLI są na Apache 2.0 — użycie komercyjne, modyfikacje i produkty zamknięte bez ograniczeń, z jawnym udzieleniem praw patentowych i obowiązkiem zachowania not licencyjnych oraz pliku NOTICE, jeśli redystrybuujesz kod. To licencja przyjazna pracy agencyjnej: nic tu nie przenosi się na kod klienta. Uwaga na resztę ekosystemu — Maestro Studio jest darmowe, ale zamknięte (kodu nie ma w repozytorium), a Maestro Cloud jest płatne (250 USD miesięcznie za urządzenie, przy miękkim limicie 20 minut na wykonanie). Oficjalna akcja GitHuba wymaga planu chmurowego; uruchomienie CLI wprost na własnym runnerze — nie.