Címke: Use AI

  • Lista az aktuális API tesztelői eszközökről

    Be kell vallanom, hogy alapvetően Postman fan vagyok. Szerintem irtó sok dolgot meg lehet benne valósítani, folyamatokat építeni, és mióta lehet AI-t kérdezni, egy-egy bonyolultabb feladat is játszi könnyedséggel megoldható vele. A gond – mármint nem nekem, hanem a cégeknek – hogy fizetni valahogy senki sem szeretne érte, illetve annyira féltik az adataikat, hogy a fizetős, felhős szolgáltatást kerülik. Nem mintha nem lenne más eszköz, ami simán adatszivárgásra adhatna okot. Na mindegy.

    Szóval megkértem az AI-t, hogy csináljon nekem egy listát az ismertebb eszközökről, ami segíthet választani a munkához, ha szükséges.

    Az API-tesztelő eszközök piaca az elmúlt években jelentősen átalakult. Míg korábban a Postman szinte egyeduralkodó volt, a kötelező felhős szinkronizáció és a szigorúbb ingyenes korlátok miatt előtérbe kerültek a pehelykönnyű, lokalitást és Git-alapú verziókövetést előnyben részesítő alternatívák (pl. Bruno, Thunder Client).

    Az alábbi összehasonlítás a legnépszerűbb eszközök előnyeit és hátrányait mutatja be.

    1. Postman

    Kinek ajánlott?Közepes és nagy csapatoknak, vállalati környezetbe, ahol teljes API életciklus-kezelésre és CI/CD automatizációra van szükség.

    • Pro (Előnyök):
      • Sokoldalú ökoszisztéma: Támogatja a REST, GraphQL, gRPC, WebSocket és SOAP protokollokat is.
      • Fejlett funkciók: Beépített Mock Serverek (szimulált végpontok), automatikus dokumentáció-generálás és API monitoring.
      • Newman CLI: Kiválóan integrálható CI/CD folyamatokba a tesztek automatikus futtatásához.
      • Erős csapatmunka: Megosztott munkaterek, szerepkörök (RBAC), kommentelés és változáskövetés.
    • Kontra (Hátrányok):
      • Erőforrás-igényes: Az Electron-alapú alkalmazás lassú lehet, és sok memóriát (RAM) fogyaszt.
      • Kötelező felhő / fiók: Az ingyenes verzióban a kollekciók jelentős része a cloudba kerül, a helyi (offline) használat erősen korlátozott.
      • Bonyolultság: A sok funkció miatt a felület túlzsúfolt lehet egyszerű napi feladatokhoz.

    2. Insomnia (by Kong)

    Kinek ajánlott?Fejlesztőknek és kisebb csapatoknak, akik letisztultabb UI-t és OpenAPI / GraphQL fókuszt keresnek.

    • Pro (Előnyök):
      • Letisztult UI: Gyorsabb és sokkal átláthatóbb felület, mint a Postmané.
      • Kiváló GraphQL támogatás: Elsőrangú intellisense, autokiegészítés és séma-feltérképezés.
      • Design-first megközelítés: Kiváló OpenAPI / Swagger specifikációk írására és tesztelésére egy helyen.
      • Native Git Sync: Közvetlenül szinkronizálható Git tárhelyekkel (GitHub, GitLab).
    • Kontra (Hátrányok):
      • Nincs beépített Mock Server: Hiányoznak a Postman-féle szimulált szerver funkciók.
      • Kisebb ökoszisztéma: Kevesebb beépített integráció és közösségi dokumentáció.
      • Változó üzleti modell: A korábbi kötelező regisztrációs döntéseik miatt a közösség egy része elvesztette benne a bizalmát (noha a local mode-ot azóta visszahozták).

    3. Bruno

    Kinek ajánlott?Git-központú fejlesztőknek, akik offline, gyors és teljesen helyi adattárolást szeretnének cloud kényszer nélkül.

    • Pro (Előnyök):
      • Git-native (Plain Text): A kollekciókat sima fájlokként (.bru formátum) tárolja a projekt saját mappa-struktúrájában, így a tesztek a kód mellett, Pull Request-ekben verziózhatók.
      • Teljesen offline & adatvédelem: Nincs cloud szinkronizáció, az adatok soha nem hagyják el a gépedet.
      • Rendkívül gyors és könnyű: Minimális erőforrást fogyaszt és pillanatok alatt elindul.
      • Open Source: Nyílt forráskódú, átlátható működésű eszköz.
    • Kontra (Hátrányok):
      • Hiányzó komplex szolgáltatások: Nincs hosted dokumentáció, mock server vagy felhős monitoring.
      • Grafikus csapatmunka hiánya: Nincsenek felhős kattintgatós workspace-ek (a csapatmunka a Git folyamatokra támaszkodik).

    4. Thunder Client (VS Code kiterjesztés)

    Kinek ajánlott?Azoknak a fejlesztőknek, akik VS Code-ban dolgoznak, és nem akarnak külön alkalmazást nyitni egy-egy API híváshoz.

    • Pro (Előnyök):
      • Beépül a szerkesztőbe: Közvetlenül a VS Code-on belül fut, nem kell váltgatni az ablakok között.
      • Gyors kontextus-váltás: Fejlesztés közben azonnal tesztelhető az épp megírt végpont.
      • Helyi tárolás: A tesztkollekciók a munkatér részeként elmenthetők JSON-ban.
    • Kontra (Hátrányok):
      • Korlátozott funkciók: Összetett scriptekre, bonyolult teszt-assert-ökre vagy terheléstesztre nem alkalmas.
      • Zárt ökoszisztéma: Csak VS Code alatt érhető el.
      • Fizetős korlátok: A haladóbb funkciókért már a Pro verzióra van szükség.

    5. HTTPie (GUI & CLI)

    Kinek ajánlott? Fejlesztőknek, akik szeretik a szintaxis-kiemeléssel ellátott, humán-barát terminálos vagy egyszerű felületes tesztelést.

    • Pro (Előnyök):
      • Emberi szintaxis: A curl-nál nagyságrendekkel egyszerűbb, olvashatóbb parancssori szintaxis.
      • Gyönyörű kimenet: Automatikus JSON formázás és színkiemelés a terminálban.
      • Asztali GUI is elérhető: Szép, modern grafikus felülettel is rendelkezik.
    • Kontra (Hátrányok):
      • Bonyolult automatizált teszt-suite-ok felépítésére és nagy vállalati projektekre kevésbé ideális.

    Összefoglaló összehasonlító táblázat

    EszközAdattárolásGit integrációBonyolultságLegfőbb erőség
    PostmanCloud-firstManuális / ExportMagasTeljes API életciklus & Mock-ok
    InsomniaHelyi + CloudNatív GitKözepesTisztaság & OpenAPI / GraphQL
    Bruno100% HelyiNatív (Plain text)AlacsonyAdatvédelem, sebesség, Git-alap
    Thunder ClientHelyi JSONProjekt fájlkéntAlacsonyVS Code-ba épített kényelem

    Összegzésként: Ha nagy csapatban dolgoztok és kell a Mock Server meg a felhős dokumentáció, a Postman a biztos választás. Ha a kód mellett szeretnétek tartani a teszteket felhős függőség nélkül, a Bruno a jelenlegi legnépszerűbb alternatíva. Ha pedig GraphQL-t használtok vagy a letisztult UI a fontos, az Insomnia a nyerő.

    És ha már itt tartunk, akkor megint egy típikus interjú kérdés az API téma.

    Az API-kat (Application Programming Interface) többféle szempont szerint csoportosíthatjuk: hozzáférhetőség, architekturális stílus/protokoll, valamint funkció/felhasználási terület alapján.

    Íme a legfontosabb kategóriák és típusaik:

    1. Hozzáférhetőség (Láthatóság) alapján

    Ez a csoportosítás azt határozza meg, hogy kik és hogyan érhetik el az API-t.

    • Privát (Belső / Internal) API-k:
      • Cél: Egy szervezet belső rendszereinek és microservice-einek egymás közötti kommunikációja.
      • Jellemzőjük: Rejtve vannak a külvilág elől, magasabb biztonsági zónában futnak (pl. vállalatirányítási rendszerek és a belső adatbázisok között).
    • Partner API-k:
      • Cél: Meghatározott üzleti partnerek közötti adatcserére szolgálnak.
      • Jellemzőjük: Nem nyilvánosak, egyedi regisztrációhoz, B2B megállapodásokhoz és szigorú hitelesítéshez (API kulcsok, OAuth) kötöttek (pl. logisztikai cégek csomagkövető rendszerei a webáruházak felé).
    • Nyilvános (Public / Open) API-k:
      • Cél: Bárki által elérhető végpontok külső fejlesztők számára.
      • Jellemzőjük: Gyakran ingyenesek vagy freemium modellben működnek (pl. időjárás-előrejelzések, OpenStreetMap).
    • Kompozit (Composite) API-k:
      • Cél: Több különböző szolgáltatás vagy adatforrás egyetlen hívásból történő elérése.
      • Jellemzőjük: Csökkentik a hálózati terhelést azzal, hogy a kliensnek nem kell 5 külön hívást indítania, hanem egyetlen összetett válaszban kapja meg az adatokat.

    2. Architekturális stílus és protokoll alapján

    Ez határozza meg az adatáramlás formátumát és a kommunikáció szabályait.

    • REST (Representational State Transfer):
      • Működés: A legelterjedtebb webes API architektúra. A meglévő HTTP metódusokra (GET, POST, PUT, DELETE) épül, leggyakrabban JSON formátumban kommunikál.
      • Előny: Skálázható, egyszerű, állapotmentes (stateless).
    • GraphQL:
      • Működés: A Facebook által kifejlesztett lekérdezőnyelv. A kliens hajszálpontosan meghatározza, milyen adatokra van szüksége egyetlen kérésben.
      • Előny: Megszünteti az under-fetching (kevés adat) és over-fetching (feleslegesen sok adat) problémáját.
    • SOAP (Simple Object Access Protocol):
      • Működés: Szigorú protokoll, amely kizárólag XML formátumot használ, és a WSDL leírófájlokra támaszkodik.
      • Előny: Magas szintű beépített biztonság (WS-Security) és tranzakciókezelés. Gyakran használják banki és vállalati környezetben.
    • gRPC (Google Remote Procedure Call):
      • Működés: A Google által fejlesztett, Protocol Buffers (Protobuf) bináris formátumot és HTTP/2-t használó keretrendszer.
      • Előny: Rendkívül gyors, kis méretű adatcsomagok, ideális microservice-ek közötti nagy sebességű kommunikációra.
    • WebSocket & Server-Sent Events (SSE):
      • Működés: Kétirányú (WebSocket) vagy egyirányú (SSE) valós idejű kommunikációs csatornák.
      • Előny: Nem kell folyamatosan újra lekérdezni a szervert (polling); tökéletes chat-alkalmazásokhoz, élő tőzsdei adatokhoz és sporteredményekhez.

    3. Funkció és felhasználási terület alapján

    • Web API-k: A fent részletezett HTTP-alapú interfészek (REST, GraphQL stb.).
    • Operációs rendszer (OS) API-k: Lehetővé teszik a szoftverek számára a hardver és az OS szolgáltatásainak elérését (pl. Windows Win32 API, POSIX, Android Camera API).
    • Könyvtár- és keretrendszer (Library) API-k: A kód szintjén biztosítanak interfészt egy adott nyelvhez vagy csomaghoz (pl. React API, Java Standard Library API).
    • Adatbázis API-k: Alkalmazások és adatbázis-kezelők közötti kommunikációra szolgálnak (pl. JDBC, ODBC).
  • Playwright 1.64 újdonságok

    A Playwright 1.64-es verziója számos olyan új funkciót és finomítást hozott, amelyek megkönnyítik az elemkeresést, a tesztek futtatását, valamint a videófelvételek testreszabását.

    1. Új Locator API: locator.within()

    A locator.within() segítségével két már létező locatort lehet természetes módon kombinálni, és a relatív pozicionálók (pl. .first(), .nth()) a szülő elemen belül értékelődnek ki.

    TypeScript

    // Összekapcsol két külön definíciót
    const dialog = page.getByRole('dialog');
    const saveButton = page.getByRole('button', { name: 'Mentés' });
    
    // Mentés gomb keresése kifejezetten a párbeszédablakon belül
    await saveButton.within(dialog).click();
    

    2. Véletlenszerű tesztfuttatás: --shuffle

    Segít kiszűrni a tesztek közötti rejtett függőségeket a futtatási sorrend megkeverésével. A tesztfutás végén megadott seed kód segítségével pontosan reprodukálható a hibás futási sorrend.

    Bash

    # Tesztek véletlenszerű sorrendben történő futtatása
    npx playwright test --shuffle
    
    # Pontos futási sorrend újrajátszása a kapott seed alapján
    npx playwright test --shuffle 271828182
    

    3. Tesztzárolások (Test Locks) csoportszinten

    Míg a 1.63-ban megjelent zárolások csak egyes tesztekre vonatkoztak, a 1.64-ben már egész fájlokra vagy tesztcsoportokra (describe) is alkalmazhatóak. Az azonos zárolási névvel rendelkező tesztek nem futnak párhuzamosan.

    TypeScript

    import { test } from '@playwright/test';
    
    // Az ebben a csoportban lévő tesztek nem futnak párhuzamosan más 'user-settings' zárral ellátott tesztekkel
    test.describe.configure({ lock: 'user-settings' });
    
    test('profil frissítése', async ({ page }) => {
      // ...
    });
    

    4. default: false beállítás a projekt konfigurációban

    A playwright.config.ts-ben megjelölhetők olyan böngészők vagy projektek, amelyeket alapértelmezetten ki szeretnél hagyni a sima npx playwright test futtatásból anélkül, meglévő konfigurációkat kellene törölni.

    TypeScript

    // playwright.config.ts
    export default {
      projects: [
        { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
        { name: 'firefox', use: { ...devices['Desktop Firefox'] }, default: false },
        { name: 'webkit', use: { ...devices['Desktop Safari'] }, default: false },
      ],
    };
    
    • Futtatás csak chromium-on: npx playwright test
    • Kifejezett futtatás firefox-on: npx playwright test --project=firefox

    5. Videófelvételek fejlesztése (VP9, CSS stílusok, FPS)

    A videók rögzítése hatékonyabbá és jobban testreszabhatóvá vált:

    • VP9 kódolás: Kisebb fájlméret és alacsonyabb CPU-használat.
    • Egyedi képkockasebesség (FPS): Testreszabható fps opció.
    • CSS stílusok a műveletjelzőkhöz: A korábbi fontSize opció helyett CSS szabályok adhatók meg a mutatók és kiemelések testreszabására.

    TypeScript

    // playwright.config.ts
    export default {
      use: {
        video: {
          mode: 'on',
          fps: 60,
          style: {
            pointMarker: 'background-color: red;',
            targetHighlight: 'border: 2px solid blue;',
          },
        },
      },
    };
    

    6. Kísérleti WebMCP támogatás (page.webmcp)

    A Playwright kiegészült a böngésző alapú AI modellek eszközeinek (WebMCP) tesztelésére szolgáló felülettel (page.webmcp.tools() és page.webmcp.callTool()), amellyel ellenőrizhetők az oldalak által az AI-ágensek felé regisztrált eszközök.

    Fontosabb breaking change-ek / Viselkedésbeli változások

    • Rejtett iframe-ek: A nem látható iframe-ben lévő elemeket (pl. visibility: hidden) a Playwright mostantól automatikusan rejtettként kezeli az akciók és a toBeVisible() ellenőrzések során.
    • Emulált képernyőméret: A devices['Desktop Chrome'] típusú eszközleírók átadják a screen tulajdonságot, így a window.screen felbontása és a media query-k az emulált kijelzőméretet tükrözik.
    • Snapshot frissítés: A --update-snapshots=missing opció használata esetén a hiányzó screenshotok létrehozása után a teszt sikeresként értékelődik.
  • Felfedező tesztelés AI-val

    Egyre lustább vagyok, és az idő is nagy ellenség lett a munkában, így sok esetben jól tud jönni az extra segítség, amit manapság AI formájában kaphatunk meg. Én személy szerint nagyon sok szakmai témában konzultálok vele, mielőbb neki kezdenek, de vannak olyan témák is, amiről kevés információm van, csak hallottam valahol, így időm engedtével rákeresek. Ilyen az AI-val történő exploratory tesztelés is.

    Az exploratory testing (magyarul felfedező tesztelés) a szoftvertesztelés egy olyan megközelítése, ahol a tesztek megtervezése, végrehajtása és a szoftver megismerése egyszerre, párhuzamosan történik.

    A hagyományos (szkriptelt) teszteléssel ellentétben – ahol a tesztelő előre megírt, lépésről lépésre követendő teszteseteket (test cases) hajt végre – a felfedező tesztelésnél a tesztelő a saját intuitív tudására, domain-ismeretére, tapasztalatára és kíváncsiságára támaszkodik.

    A felfedező tesztelés fő jellemzői:

    1. Szabadság és rugalmasság: Nincsenek szigorúan előre leírt tesztlépések. A tesztelő menet közben dönt arról, hogy az alkalmazás melyik részét milyen irányban vizsgálja tovább, reagálva a szoftver tényleges viselkedésére.
    2. Tanulás és alkalmazkodás: Ahogy a tesztelő használja a szoftvert, egyre jobban megérti annak működését és gyenge pontjait. Ha a szoftver egy ponton gyanúsan viselkedik, a tesztelő azonnal mélyebbre áshat abban a témában.
    3. Kreativitás és hibatalálgatás (Error Guessing): Olyan egyedi, nem szokványos felhasználói útvonalakat és szélsőséges eseteket (edge case-eket) igyekszik feltárni, amelyekre a fejlesztők vagy a specifikációt írók nem gondoltak.
    4. Alacsony előkészítési igény: Mivel nem kell előre bonyolult tesztdokumentációt írni, nagyon gyorsan elindítható, így ideális agilis (Agile/Scrum) környezetben.

    Mit nem jelent az exploratory tesztelés?

    • Nem ad-hoc kattintgatás („monkey testing”): A felfedező tesztelés nem céltalan próbálkozás. Bár nincs előre megírt forgatókönyv, a tesztelőnek világos célja és stratégiája van.
    • Célzott keretek (Session-Based Testing): A gyakorlatban a felfedező tesztelést gyakran strukturált keretek között végzik:
      • Charter (Küldetés): Meghatározzák a tesztelés célját (pl. „Fedezd fel a fizetési folyamatot a lejárt bankkártyák kezelésének szempontjából”).
      • Időkeret (Time-box): A tesztelést egy rögzített idősávra korlátozzák (pl. 60–90 perc).
      • Jegyzőkönyv / Összefoglaló: A tesztelő rögzíti, mit tapasztalt, milyen problémákat talált és merre érdemes tovább kutatni.

    Összességében a felfedező tesztelés nem a szkriptelt vagy automatizált tesztek helyettesítője, hanem azok kiegészítője: az automatizált tesztek ellenőrzik, hogy a már meglévő funkciók működnek-e, míg a felfedező tesztelés segít megtalálni az új, váratlan hibákat.

    Az exploratory (felfedező) tesztelés természeténél fogva egy ember-központú, kreatív folyamat, ahol a tesztelő intuíciója, domain-ismerete és kíváncsisága vezet. Az AI itt nem a tesztelőt váltja fel, hanem hihetetlenül hatékony asszisztensként (co-pilot) működik, amely leveszi a válláról a monoton feladatokat, és új, nem várt tesztforgatókönyveket javasol.

    Mivel hallottam róla, hogy vannak, akik AI segítéséggel csinálják ezt, megkérdeztem az AI-t, hogy mit tud róla, és kaptam is egy listát. Ahogy lesz időm, kedvem, bele fogom ásni magam a témába.

    Addig is itt a válasz a Gemini-től:

    Az AI-alapú exploratory tesztelés megközelítései és eszközei méret és cél szerint három fő kategóriába oszthatók:

    1. AI-alapú Böngésző- és Autonóm Tesztelő Ügynökök (Autonomous AI Testing Agents)

    Ezek az eszközök képesek önállóan „bejárni” a webalkalmazásokat, feltérképezni a felületet, hibákat keresni, és közben segítik a felfedezést.

    • Open-source / DIY megközelítés (Playwright + LLM-alapú keretrendszerek):
      • Stagehandvagy Browser-use / Explorbot: Ezek modern, nyílt forráskódú keretrendszerek, amelyek ráköthetők a nagyobb nyelvi modellekre (pl. Claude vagy GPT). Természetes nyelven vagy célok megadásával (pl. „Keresd meg az űrlapon a hiányzó validációkat”) képesek irányítani a böngészőt, elemezni a DOM-ot, és olyan edge case-eket (szélsőséges eseteket) is letesztelni, amiket egy hagyományos script nem érne el.
    • Kereskedelmi platformok (Mabl, Functionize, Testim):
      • Ezekben az AI-alapú platformokban van ún. Agentic vagy intelligens felfedező funkció is.Amikor új funkciót raksz ki, az AI képes önállóan „körbejárni” az oldalt, vizuális anomáliákat és hibaüzeneteket keresni a konzolon, így támogatva az exploratory tesztelési fázist.

    2. AI Asszisztensek és Teszttervező Co-pilotok (Ötleteléshez és Charterekhez)

    Ha hagyományos, session-based exploratory tesztelést (SBET) csinálsz, az AI kiválóan használható partnerré válik a tesztelési „charterek” és ötletek generálásában.

    • ChatGPT / Claude / Cursor (Prompt alapú teszttervezés):
      • Hogyan használd? Másold be egy új funkció specifikációját vagy user story-ját, és kérd meg az AI-t: „Készíts nekem 5 darab exploratory testing charter-t a Boundary Value Analysis (határérték-elemzés) és a Error Guessing (hibatalálgatás) heurisztikái alapján erre a funkcióra.”
      • Segít feltárni olyan logikai vakfoltokat, amikre fejlesztőként vagy tesztelőként nem gondoltál volna.

    3. Vizuális és Interaktív AI Eszközök

    • Applitools (Eyes):
      • Exploratory tesztelés közben a legnagyobb ellenség a vizuális hiba, ami elkerüli a figyelmet. Az Applitools AI-alapú vizuális motorja azonnal észleli a renderelési hibákat, elcsúszásokat bármilyen eszközön, miközben te szabadon kattintgatsz és tesztelsz.

    Mit érdemes választani?

    1. Ha gyorsan és olcsón akarsz egyedi megoldást: Használj LLM alapú szkripteket / open-source keretrendszereket (Stagehand vagy custom Playwright szkriptek AI integrációval), amikkel autonóm módon át tudod vizsgáltatni a felületet.
    2. Ha strukturált, emberi exploratory tesztelést végzel: Használj AI-t (Claude/GPT) ötletgenerálásra és kockázatelemzésre a tesztelés előtt (hová érdemes kaparni, milyen inputokat próbálj ki), majd a session végén rögzítsd a tapasztalatokat egy Test Management eszközben (pl. Xray vagy TestRail), ahol az AI összefoglalja a bug riportokat.
  • JMeter – 50.000 virtuális user

    JMeter – 50.000 virtuális user

    Megkérdeztem a Gemini-t, hogy megoldható-e, és ha igen, hogyan egy 50.000 useres performancia tesztelés az Apache JMeter-rel. A válasz itt következik:

    Igen, teljesen megoldható, de van egy kritikus technikai feltétel: egyetlen gépről (például egy laptopról vagy egyetlen virtuális gépről) nem lehet elindítani 50 000 egyidejű felhasználót, mert a hardveres korlátok (CPU, RAM, hálózati socketek kimerülése) miatt maga a tesztelő eszköz fog összeomlani. Ehhez elosztott (distributed) architektúrára vagy felhőalapú futtatásra lesz szükség.

    Így kezdenék neki lépésről lépésre:

    1. Célok és workload modell tisztázása

    • Pontosan mit jelent az 50 000 user? 50 000 egyidejűleg aktív felhasználó, akik folyamatosan kattintgatnak, vagy 50 000 egyidejű kapcsolat (session), de az aktivitásuk eloszlik?
    • Milyen a felhasználói útvonal (User Journey)? Nem mindegy, hogy csak a főoldalt hívják le (ez könnyű), vagy bejelentkeznek, keresnek, terméket raknak a kosárba és fizetnek (ez bonyolultabb, adatbázis- és tranzakcióigényes).
    • Milyen mérőszámokat vársz el? (pl. 95%-os válaszidő 2 másodperc alatt, 0% hibaarány mellett).

    2. A tesztinfrastruktúra felépítése (Distributed JMeter)

    Mivel egy átlagos JMeter worker gép maximum 1000–2000 szálat bír stabilan, 50 000 userhez 25-50 darab worker gépre lesz szükség:

    • Master-Worker (Slave) felállás: Egy központi JMeter Master irányítja a tesztet, és gyűjti az adatokat a worker gépekről (ezek lehetnek olcsó AWS EC2 instance-ok vagy Docker konténerek).
    • Alternatíva: Használhatsz dedikált terheléses felhőszolgáltatásokat is (pl. BlazeMeter), amelyek mögött ugyanez az elv fut, de nem kell manuálisan menedzselni a 30-40 szervert.

    3. A JMeter script megírása

    • Thread Group beállítások: A 50 000 szálat érdemes fokozatosan ráengedni a rendszerre (Ramp-up period, pl. 30–45 perc alatt), hogy látsd, hol kezd el lassulni a rendszer.
    • Valósághű viselkedés: Használj Think Time-ot (szüneteket a kérések között), különben a szkript robotként viselkedik, amit egy WAF (Web Application Firewall) vagy cache réteg azonnal blokkolhat.
    • Dinamikus adatok:CSV Data Set Config segítségével biztosítsd, hogy a felhasználók különböző nevekkel, jelszódal vagy tokenekkel jelentkezzenek be, ne ugyanazzal az egy fiókkal indítsa mind az 50k szál a kérést.

    4. Szerveroldali monitorozás bekötése (Monitoring)

    A JMeter csak azt mutatja meg, mit tapasztal a felhasználó (válaszidő, hiba), azt nem, hogy miért lassult be a szerver. Ehhez kell a backend monitoring:

    • Alkalmazásszerverek, adatbázisok, cache (Redis) CPU, RAM és I/O terhelésének figyelése (pl. Prometheus + Grafana vagy Datadog).
    • Adatbázis lassan futó lekérdezéseinek és connection pool telítettségének mérése.

    5. Tesztfutás és finomhangolás

    • Soha ne használd a JMeter GUI-t (grafikus felületet) terheléses teszt alatt! Csak parancssorból, non-gui módban futtasd a master gépet.
    • Fokozatosság elve: Ne 50 000 userrel kezdd. Először futtass le egy 1000 useres tesztet, javítsd ki az azonnal előjövő hibákat, majd skálázd fel 5000-re, 10 000-re, és így tovább, ambron eléred a célt.

    Milyen típusú rendszert (pl. webshop, REST API, banki backend) kell tesztelni, és milyen környezetben fut jelenleg?

    Egy REST API terheléses tesztelése 50 000 felhasználóval specifikus technikai kihívásokat rejt, amelyekre különösen figyelned kell a JMeter konfigurálása során:

    1. Hitelesítés és token-kezelés (Authentication)

    A REST API-k túlnyomó többsége token alapú (JWT vagy OAuth2).

    • Ne kérj le tokent minden egyes kérésnél: Ha mind az 50 000 szál a POST /api/login végponttal kezdi a tesztet, azzal azonnal túlterheled a hitelesítőszervert, még mielőtt a valódi API funkciókat tesztelnéd.
    • Megoldás: Előre generálj le egy token-készletet (pl. egy külön scripttel vagy adatbázis-lekérdezéssel), töltsd be egy CSV fájlba, és a JMeterben ezt olvastasd be. Így a felhasználók már eleve érvényes tokenekkel küldhetik a kéréseket.

    2. HTTP Request Defaults és Keep-Alive

    Magas koncurrencia esetén kritikus a hálózati kapcsolatok hatékonysága:

    • HTTP Request Defaults: Állítsd be a központi URL-t, portot és a Content-Type-ot (application/json), hogy ne kelljen minden egyes kérésnél megismételni.
    • Keep-Alive: A JMeter alapértelmezésben engedélyezi a Keep-Alive-ot. REST API-knál ez elengedhetetlen, mert így nem nyit és zár le TCP kapcsolatot minden egyes apró API híváshoz, ami drasztikusan csökkenti a hálózati terhelést a kliens oldalon, és valósághűbb képet ad a szerver teljesítményéről.

    3. Dinamikus adatok és JSON Extractor

    Egy REST API láncolt hívásokból áll (pl. létrehozol egy erőforrást POST-tal, majd annak az ID-jét lekéred GET-tel).

    • Használj JSON Extractor elemet a válaszokból (pl. létrehozott objektum ID-je, státusza) való adatkigyűjtésre.
    • Ezeket az értékeket mentsd el JMeter változókba (${id}), hogy a következő kérésben dinamikusan fel tudd használni.

    4. Payload (Adatcsomag) optimalizálás

    • Ha a tesztben nagy JSON payloadokat küldesz (pl. tömeges adatfeltöltés), kerüld a bonyolult, dinamikusan generált szövegeket a JMeter belső függvényeivel, mert az leterheli a JMeter saját CPU-ját.
    • A tesztadatokat (JSON törzseket) előre generált fájlokból (CSV vagy JSON file) olvasd be, így a worker gépek erőforrásai tisztán a hálózati kérésekre fognak fókuszálni.

    5. Válaszidő és aszinkronitás

    • Határozd meg, hogy az API-n belül vannak-e aszinkron műveletek (pl. üzenetssorba, RabbitMQ-ba vagy Kafka-ba küldött események). Ha igen, a válaszidő alacsony lehet (mert a szerver csak átvette az üzenetet), de a valódi feldolgozást a háttérben lévő worker service-eknek kell bírniuk, így a monitoringot kötelező kiterjeszteni a háttérfolyamatokra is.

    Szerintem elég jól megválaszolta a kérdést. Egy részével tisztában voltam, sejtettem, de akadt jó pár jó tanács is. Gondoltam megkérdezem, hogy JMeteren kívül milyen mást eszközt ajánlana ehhez.

    Ha a JMeter elosztott architektúrájának (a 25-50 worker gépnek a) manuális konfigurálása és karbantartása túl bonyolultnak tűnik, a modern teljesítménytesztelő eszköztárban kifejezetten REST API-khoz sokkal gördülékenyebb megoldások is léteznek:

    1. k6 (Grafana k6) – A legjobb választás REST API-khoz

    A k6 jelenleg a legnépszerűbb fejlesztőbarát alternatíva. C-ben íródott (Go runtime-mal), így nagyságrendekkel kevesebb memóriát és CPU-t fogyaszt, mint a Java-alapú JMeter.

    • Miért ideális 50k userhez? Egyetlen erősebb szerverről lényegesen több szálat (virtuális usert) tud futtatni, mint a JMeter, így kevesebb infrastruktúrára lesz szükséged.
    • Szkriptelés: JavaScriptben írod a teszteket, ami nagyon természetes a REST API JSON válaszok és dinamikus tokenek kezelésénél.
    • Integráció: Kiemelkedően jó Grafana, Prometheus és CI/CD (GitHub Actions, GitLab) támogatása van.

    2. Gatling – Ha elképesztő skálázhatóságra és szép riportokra van szükség

    A Gatling aszinkron (Netty-alapú) architektúrát használ, így nem rendel egy külön operációs rendszer szálat minden egyes felhasználóhoz.

    • Miért ideális 50k userhez? Brutálisan jó az erőforrás-kihasználása. Egyetlen közepes gépről képes több tízezer egyidejű kapcsolatot felépíteni és kezelni.
    • Szkriptelés: Scala, Java vagy Kotlin nyelven írhatsz szép, olvasható domain-specifikus kódot (DSL).
    • Riportok: Alapból nagyon részletes és vizuálisan tetszetős HTML riportokat generál a teszt végén.

    3. Locust – Ha Python-alapú veremből dolgozol

    A Locust egy nyílt forráskódú, Python-alapú tesztelőeszköz, ahol a felhasználói viselkedést tiszta Python kódban definiálod.

    • Miért ideális 50k userhez? Nagyon egyszerűen összefűzhető elosztott (distributed) módba (Master-Worker architektúra), és menet közben, a böngészős UI-ról is állítható a terhelés mértéke.
    • Előny: Ha a csapat már ismeri a Pythont, a tesztek megírása és karbantartása minimális időt vesz igénybe.

    Menedzselt felhős alternatívák (SaaS)

    Ha egyáltalán nem szeretnél szervereket bérelni és konfigurálni:

    • Grafana Cloud k6 / BlazeMeter / Loader.io / AWS Distributed Load Testing:Ezeknél a szolgáltatásoknál csak feltöltöd a k6, JMeter vagy saját konfigurációs scriptjeidet, kiválasztod a régiót és a 50 000 felhasználós terhelést, ők pedig a háttérben azonnal felhúzzák a szükséges infrastruktúrát, majd a teszt végén leállítják azt.

    Összegzés – Melyiket válaszd?

    • Ha kódból írnál teszteket és a legjobb erőforrás-kihasználást akarod minimalizált szerverköltséggel: k6 vagy Gatling.
    • Ha a csapat Pythonban erős: Locust.
    • Ha nulla infrastruktúra-üzemeltetést szeretnél: Grafana Cloud k6 vagy BlazeMeter.
  • Tipikus interjú kérdés: yield

    Mennyi mindent használunk, de igazából lehet, hogy fogalmunk sincs róla, hogy mire is jó pontosan, és mikor is érdemes használni. A miértről nem is beszélve…

    Mit mesél erről a Gemini?

    A .NET-ben (C#) a yield kulcsszó pontosan ugyanazt a célt szolgálja, mint a Pythonban: IEnumerable<T> vagy IAsyncEnumerable<T> típusú adatfolyamokat hozhatsz vele létre úgy, hogy a kód lusta kiértékeléssel (lazy evaluation), elemenként adja vissza az eredményeket.

    C#-ban két formája létezik:

    • yield return <érték>: Visszaad egy elemet, majd felfüggeszti a metódus futását a következő elem kéréséig.
    • yield break: Azonnal leállítja az iterációt (megszakítja a generálást).

    Mire jó C#-ban?

    1. Memóriatakarékosság nagy adathalmazoknál

    Ha nem List<T>-et adsz vissza, nem kell a teljes gyűjteményt lefoglalni a RAM-ban.

    C#

    using System;
    using System.Collections.Generic;
    
    // PÉLDA 1: Hagyományos megközelítés (Memóriatakaréktalan)
    public List<int> GetNumbersList()
    {
        var list = new List<int>();
        for (int i = 0; i < 1_000_000; i++)
        {
            list.Add(i); // Mind az 1 milló elem belekerül a RAM-ba
        }
        return list;
    }
    
    // PÉLDA 2: yield használata (Memóriatakarékos)
    public IEnumerable<int> GetNumbersYield()
    {
        for (int i = 0; i < 1_000_000; i++)
        {
            yield return i; // Mindig csak az aktuális számot generálja le
        }
    }
    

    2. Szűrés és adatfeldolgozás yield break-kel

    A yield break használatával feltételhez kötheted az adatfolyam végét anélkül, hogy bonyolult feltételkezelést kellene írnod a ciklus köré:

    C#

    public IEnumerable<string> GetActiveUsers(List<string> users)
    {
        foreach (var user in users)
        {
            if (user == "ADMIN_STOP")
            {
                yield break; // Azonnal leállítja az elemek kiadását
            }
    
            if (!string.IsNullOrEmpty(user))
            {
                yield return user;
            }
        }
    }
    

    3. Óriási fájlok feldolgozása soronként

    Nem kell a gigabájtos fájlokat egyszerre a memóriába tölteni (File.ReadAllLines helyett):

    C#

    using System.IO;
    using System.Collections.Generic;
    
    public IEnumerable<string> ReadLargeFile(string filePath)
    {
        using (StreamReader reader = new StreamReader(filePath))
        {
            string? line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line; // Mindig csak egyetlen sort tart a memóriában
            }
        }
    }
    
    // Használat:
    foreach (string line in ReadLargeFile("huge_log.txt"))
    {
        if (line.Contains("ERROR"))
        {
            Console.WriteLine(line);
        }
    }
    

    4. Aszinkron adatfolyamok (C# 8.0+: IAsyncEnumerable<T>)

    Modern .NET-ben hálózati kéréseknél vagy adatbázis-lekérdezéseknél aszinkron módon is használhatod:

    C#

    public async IAsyncEnumerable<int> FetchDataAsync()
    {
        for (int i = 1; i <= 5; i++)
        {
            await Task.Delay(1000); // Pl. hálózati várakozás szimulálása
            yield return i;
        }
    }
    
    // Használat:
    await foreach (var item in FetchDataAsync())
    {
        Console.WriteLine(item); // Másodpercenként jelenik meg egy elem
    }
    

    Hagyományos visszatérés vs. yield C#-ban

    SzempontList<T> / Tömbyield return (IEnumerable<T>)
    Kiértékelés idejeAzonnali (A metódus hívásakor lefut a teljes ciklus)Lusta / Halasztott (Csak akkor fut, amikor a foreach kéri a következő elemet)
    MemóriahasználatMagas ($O(N)$ méretű lista a RAM-ban)Minimális ($O(1)$ állandó memóriagény)
    Sebesség (első elem)Lassú (meg kell várni a teljes lista felépítését)Azonnali (az első elem kérésére rögtön visszatér)

    Gondoltam, megkérdezem ugyanezt Typescript esetén.

    Igen, pontosan ugyanígy működik TypeScriptben és JavaScriptben is.

    A TypeScript a JavaScript generátor függvényeit (Generator Functions) használja, amelyeket az function* (csillaggal jelölt) szintaxisról lehet felismerni.

    A működési elv teljesen megegyezik a Python és a C# esetében látottakkal: a yield felfüggeszti a függvény futását, megőrzi a belső állapotot, és lusta kiértékeléssel (lazy evaluation) adja vissza az elemeket egy Generator objektumon keresztül.

    Alapvető példa TypeScriptben

    A generátor függvény visszatérési típusa Generator<T, TNext TReturn,>, ahol a T a yield-elt értékek típusa.

    TypeScript

    // A csillag (function*) jelzi, hogy ez egy generátor függvény
    function* szamGeneraltor(): Generator<number> {
      yield 1;
      yield 2;
      yield 3;
    }
    
    // Használat for...of ciklussal:
    for (const szam of szamGeneraltor()) {
      console.log(szam); // Kiírja egymás után: 1, 2, 3
    }
    

    Mire jó TypeScriptben?

    1. Memóriatakarékos és végtelen sorozatok

    Nem kell előre tömbbe gyűjteni az elemeket:

    TypeScript

    function* vegtelenAzonositoGeneral(): Generator<string> {
      let id = 1;
      while (true) {
        yield `ID_${id++}`;
      }
    }
    
    const idGen = vegtelenAzonositoGeneral();
    
    console.log(idGen.next().value); // "ID_1"
    console.log(idGen.next().value); // "ID_2"
    console.log(idGen.next().value); // "ID_3"
    

    2. Adatfolyamok delegálása (yield*)

    A TypeScriptben létezik a yield* utasítás is, amellyel átadhatod a vezérlést egy másik generátornak vagy tömbnek (bejárható objektumnak):

    TypeScript

    function* elsoResz(): Generator<number> {
      yield 1;
      yield 2;
    }
    
    function* teljesSorozat(): Generator<number> {
      yield* elsoResz(); // Delegálja az elemeket az elsoResz-ből
      yield* [3, 4, 5];  // Tömb elemeit is ki tudja adni egymás után
    }
    
    console.log([...teljesSorozat()]); // [1, 2, 3, 4, 5]
    

    3. Aszinkron generátorok (yield + async)

    Ahogy C#-ban az IAsyncEnumerable, TypeScriptben és JS-ben is létezik az AsyncGenerator (async function*). Ez hálózati adatok, lapozott API-k vagy adatfolyamok (stream) feldolgozásánál nagyon hasznos:

    TypeScript

    async function* adatokLetolteseAPIrol(): AsyncGenerator<string> {
      const oldalak = [1, 2, 3];
      
      for (const oldal of oldalak) {
        // Szimulált hálózati kérés (API hívás)
        const valasz = await fetch(`https://api.example.com/data?page=${oldal}`);
        const adat = await valasz.text();
        
        yield adat; // Mindig csak az aktuális oldal adatait adja vissza
      }
    }
    
    // Használat for await...of ciklussal:
    async function feldolgozas() {
      for await (const oldalAdat of adatokLetolteseAPIrol()) {
        console.log("Megérkezett egy oldal:", oldalAdat);
      }
    }
    

    Összehasonlítás

    NyelvFüggvény jelöléseKulcsszóBejárási felület
    C#IEnumerable<T> Get()yield returnforeach / LINQ
    Pythondef get():yieldfor item in gen
    TypeScriptfunction* get(): Generator<T>yieldfor...of / next()
  • Grafana K6 készítés Copilot-tal

    Mi is történt? Ismét a semmiből kezdtem, megkértem a Copilot-ot, hogy csináljon egy project-et. El is készült, futott, de a riportolásával nem voltam elégedett. Megkérdeztem, hogy mit ajánl rá, ő elsőre a Grafana felhős szolgáltatását ajánlotta, de mivel lokálból akartam megoldani, így kiegyeztünk a Grafana lokális verziójával. Telepített Grafanát, Dockert, InfluxDB-t. Még a Grafana dashboard-ot is vele csináltattam meg. Még koránt sincs kész, de majd megfelelő Token birtoklásával orvosolni fogom.

    Teszt kód:

    import http from 'k6/http';
    import { check, sleep } from 'k6';
    
    export const options = {
      stages: [
        { duration: '40s', target: 2 },
        { duration: '35s', target: 10 },
        { duration: '60s', target: 3 },
      ],
      thresholds: {
        http_req_failed: ['rate<0.01'],
        http_req_duration: ['p(95)<500'],
      },
    };
    
    const baseUrl = __ENV.BASE_URL || 'https://test.k6.io';
    
    export default function () {
      const response = http.get(baseUrl);
    
      check(response, {
        'status is 200': (result) => result.status === 200,
        'response is HTML': (result) => result.headers['Content-Type'].includes('text/html'),
      });
    }

    Futtatás:

    D:\Repositories\ai-k6> npm run test:grafana
    
    > ai-k6@1.0.0 test:grafana
    > npm run build && node scripts/run-grafana.mjs
    
    
    > ai-k6@1.0.0 build
    > esbuild src/smoke.ts --bundle --format=esm --platform=neutral --external:k6 --external:k6/http --outfile=dist/smoke.js
    
    
      dist\smoke.js  670b 
    
    Done in 4ms
    
             /\      Grafana   /‾‾/  
        /\  /  \     |\  __   /  /   
       /  \/    \    | |/ /  /   ‾‾\ 
      /          \   |   (  |  (‾)  |
     / __________ \  |_|\_\  \_____/ 
    
    
         execution: local
            script: dist/smoke.js
            output: InfluxDBv1 (http://localhost:8086)
    
         scenarios: (100.00%) 1 scenario, 10 max VUs, 2m45s max duration (incl. graceful stop):
                  * default: Up to 10 looping VUs for 2m15s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
    
    
    
      █ THRESHOLDS 
    
        http_req_duration
        ✓ 'p(95)<500' p(95)=135.05ms
    
        http_req_failed
        ✓ 'rate<0.01' rate=0.00%
    
    
      █ TOTAL RESULTS 
    
        checks_total.......: 1138    8.391855/s
        checks_succeeded...: 100.00% 1138 out of 1138
        checks_failed......: 0.00%   0 out of 1138
    
        ✓ status is 200
        ✓ response is HTML
    
        HTTP
        http_req_duration..............: avg=72.84ms min=184µs med=114.54ms max=1s    p(90)=132.88ms p(95)=135.05ms
          { expected_response:true }...: avg=72.84ms min=184µs med=114.54ms max=1s    p(90)=132.88ms p(95)=135.05ms
        http_req_failed................: 0.00%  0 out of 1138
        http_reqs......................: 1138   8.391855/s
    
        EXECUTION
        iteration_duration.............: avg=1.15s   min=1.12s med=1.14s    max=2.02s p(90)=1.15s    p(95)=1.16s   
        iterations.....................: 569    4.195928/s
        vus............................: 4      min=1         max=10
        vus_max........................: 10     min=10        max=10
    
        NETWORK
        data_received..................: 2.2 MB 16 kB/s
        data_sent......................: 86 kB  637 B/s
    
    
    
    
    running (2m15.6s), 00/10 VUs, 569 complete and 0 interrupted iterations
    default ✓ [======================================] 00/10 VUs  2m15s
    PS D:\Repositories\ai-k6> 

    Grafana

  • Custom item list plugin AI-val generálva

    Tegnap egész sok mindennel kísérleteztem, majd eszembe jutott, hogy szerettem volna már egy ideje saját adatokat menteni a WordPress blog-omba, de sose volt elég időm, kedvem újra elővenni a PHP-t, és belekezdeni a fejlesztésbe. Szóval most, hogy van AI meg Gemini, megkértem, hogy csináljon egy alap plugin-t. Semmi extra, egy egyszerű CRUD, ahol a create, update, delete az admin felületen keresztül történik, a read pedig short code-ként kirakható bárhová.

    Azt azért látom, hogy ahhoz, ami a végső célom lenne, be kell húznom egy IDE-t, és abban prompt-olva a fejlesztést, de szerintem ez így elsőre nem tűnik rossznak.

    Az eredmény ez lett:

    NévURLTípus

    A generált kód pedig így néz ki:

    <?php
    /**
     * Plugin Name: Custom Items Manager
     * Description: CRUD bővítmény egyedi táblával, admin authentikációval és publikus szűrhető UI-val (Név, URL, Típus).
     * Version: 1.0
     * Author: Developer
     */
    
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class CustomItemsManager {
        private $table_name;
    
        public function __construct() {
            global $wpdb;
            $this->table_name =$wpdb->prefix . 'custom_items';
    
            // Telepítéskor adatbázis tábla létrehozása
            register_activation_hook(__FILE__, array($this, 'activate'));
    
            // Admin menü létrehozása
            add_action('admin_menu', array($this, 'add_admin_menu'));
    
            // Shortcode a publikus felülethez
            add_shortcode('custom_items_list', array($this, 'render_public_ui'));
    
            // AJAX keresés végpontok (publikus és bejelentkezett felhasználóknak is)
            add_action('wp_ajax_filter_custom_items', array($this, 'ajax_filter_items'));
            add_action('wp_ajax_nopriv_filter_custom_items', array($this, 'ajax_filter_items'));
        }
    
        // Adatbázis tábla létrehozása
        public function activate() {
            global $wpdb;
            $charset_collate =$wpdb->get_charset_collate();
    
            $sql = "CREATE TABLE {$this->table_name} (             id INT(11) NOT NULL AUTO_INCREMENT,             name VARCHAR(255) NOT NULL,             url VARCHAR(255) NOT NULL,             type VARCHAR(100) NOT NULL,             created_at DATETIME DEFAULT CURRENT_TIMESTAMP,             PRIMARY KEY  (id)         )$charset_collate;";
    
            require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
            dbDelta($sql);
        }
    
        // Admin menü regisztrációja
        public function add_admin_menu() {
            add_menu_page(
                'Elemek Kezelése',
                'Elemek Kezelése',
                'manage_options', // Csak adminisztrátorok érhetik el
                'custom-items-manager',
                array($this, 'render_admin_page'),
                'dashicons-database',
                20
            );
        }
    
        // ADMIN UI: Create, Read, Update, Delete
        public function render_admin_page() {
            if (!current_user_can('manage_options')) {
                wp_die(__('Nincs jogosultságod az oldal megtekintéséhez.'));
            }
    
            global $wpdb;
            $message = '';
    
            // TÖRLÉS (Delete)
            if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
                $id = intval($_GET['id']);
                check_admin_referer('delete_item_' . $id);
                $wpdb->delete($this->table_name, array('id' => $id));$message = '<div class="updated"><p>Elem sikeresen törölve.</p></div>';
            }
    
            // MENTÉS / MÓDOSÍTÁS (Create / Update)
            if (isset($_POST['submit_item'])) {
                check_admin_referer('custom_item_form_nonce');
    
                $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
                $name = sanitize_text_field($_POST['name']);
                $url = esc_url_raw($_POST['url']);
                $type = sanitize_text_field($_POST['type']);
    
                if (!empty($name) && !empty($url) && !empty($type)) {
                    if ($id > 0) {                     // Update$wpdb->update($this->table_name, array('name' =>$name, 'url' => $url, 'type' =>$type), array('id' => $id));$message = '<div class="updated"><p>Elem frissítve!</p></div>';
                    } else {
                        // Create
                        $wpdb->insert($this->table_name, array('name' => $name, 'url' =>$url, 'type' => $type));$message = '<div class="updated"><p>Új elem hozzáadva!</p></div>';
                    }
                } else {
                    $message = '<div class="error"><p>Minden mező kitöltése kötelező!</p></div>';
                }
            }
    
            // Szerkesztendő elem lekérése, ha van
            $edit_item = null;
            if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
                $edit_item =$wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", intval($_GET['id'])));
            }
    
            // Összes elem lekérdezése az admin listához
            $items = $wpdb->get_results("SELECT * FROM {$this->table_name} ORDER BY id DESC");
    
            ?>
            <div class="wrap">
                <h1>Elemek Kezelése (Admin CRUD)</h1>
                <?php echo $message; ?>
    
                <!-- CRUD Űrlap (Create/Update) -->
                <h2><?php echo $edit_item ? 'Elem Szerkesztése' : 'Új Elem Hozzáadása'; ?></h2>
                <form method="post" style="background:#fff; padding:15px; border:1px solid #ccc; max-width:500px; margin-bottom:20px;">
                    <?php wp_nonce_field('custom_item_form_nonce'); ?>
                    <input type="hidden" name="item_id" value="<?php echo $edit_item ? $edit_item->id : 0; ?>">
                    
                    <p>
                        <label>Név:</label><br>
                        <input type="text" name="name" value="<?php echo $edit_item ? esc_attr($edit_item->name) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>URL:</label><br>
                        <input type="url" name="url" value="<?php echo $edit_item ? esc_attr($edit_item->url) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>Típus:</label><br>
                        <input type="text" name="type" value="<?php echo $edit_item ? esc_attr($edit_item->type) : ''; ?>" placeholder="pl. Blog, Bővítmény, Dokumentáció" required style="width:100%;">
                    </p>
                    <p>
                        <input type="submit" name="submit_item" class="button button-primary" value="<?php echo $edit_item ? 'Frissítés' : 'Mentés'; ?>">
                        <?php if ($edit_item): ?>
                            <a href="?page=custom-items-manager" class="button">Mégse</a>
                        <?php endif; ?>
                    </p>
                </form>
    
                <!-- Admin Lista (Read) -->
                <h2>Létező Elemek</h2>
                <table class="wp-list-table widefat fixed striped">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Név</th>
                            <th>URL</th>
                            <th>Típus</th>
                            <th>Műveletek</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if ($items): foreach ($items as$item): ?>
                            <tr>
                                <td><?php echo $item->id; ?></td>
                                <td><strong><?php echo esc_html($item->name); ?></strong></td>
                                <td><a href="<?php echo esc_url($item->url); ?>" target="_blank"><?php echo esc_html($item->url); ?></a></td>
                                <td><?php echo esc_html($item->type); ?></td>
                                <td>
                                    <a href="?page=custom-items-manager&action=edit&id=<?php echo $item->id; ?>" class="button button-small">Szerkesztés</a>
                                    <a href="<?php echo wp_nonce_url('?page=custom-items-manager&action=delete&id=' . $item->id, 'delete_item_' .$item->id); ?>" class="button button-small button-link-delete" onclick="return confirm('Biztosan törlöd?');">Törlés</a>
                                </td>
                            </tr>
                        <?php endforeach; else: ?>
                            <tr><td colspan="5">Nincs megjeleníthető elem.</td></tr>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
            <?php
        }
    
        // PUBLIKUS UI: Shortcode kimenet és szűrő felület
        public function render_public_ui() {
            ob_start();
            ?>
            <div id="custom-items-public-container" style="max-width:800px; margin: 0 auto;">
                <!-- Szűrő Mezők -->
                <div style="display:flex; gap:10px; margin-bottom:20px; flex-wrap:wrap;">
                    <input type="text" id="filter-name" placeholder="Szűrés név alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-url" placeholder="Szűrés URL alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-type" placeholder="Szűrés típus alapján..." style="flex:1; padding:8px;">
                </div>
    
                <!-- Eredmények Táblázata -->
                <table style="width:100%; border-collapse:collapse; text-align:left;">
                    <thead>
                        <tr style="background:#f4f4f4; border-bottom:2px solid #ddd;">
                            <th style="padding:10px;">Név</th>
                            <th style="padding:10px;">URL</th>
                            <th style="padding:10px;">Típus</th>
                        </tr>
                    </thead>
                    <tbody id="custom-items-results">
                        <!-- AJAX tölti fel -->
                    </tbody>
                </table>
            </div>
    
            <script>
            document.addEventListener('DOMContentLoaded', function() {
                const nameInput = document.getElementById('filter-name');
                const urlInput = document.getElementById('filter-url');
                const typeInput = document.getElementById('filter-type');
                const resultsBody = document.getElementById('custom-items-results');
    
                function fetchItems() {
                    const params = new URLSearchParams({
                        action: 'filter_custom_items',
                        name: nameInput.value,
                        url: urlInput.value,
                        type: typeInput.value
                    });
    
                    fetch('<?php echo admin_url('admin-ajax.php'); ?>?' + params.toString())
                        .then(response => response.json())
                        .then(data => {
                            resultsBody.innerHTML = '';
                            if (data.length > 0) {
                                data.forEach(item => {
                                    const row = `
                                        <tr style="border-bottom:1px solid #eee;">
                                            <td style="padding:10px;"><strong>${escapeHtml(item.name)}</strong></td>
                                            <td style="padding:10px;"><a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${escapeHtml(item.url)}</a></td>
                                            <td style="padding:10px;">${escapeHtml(item.type)}</td>
                                        </tr>`;
                                    resultsBody.innerHTML += row;
                                });
                            } else {
                                resultsBody.innerHTML = '<tr><td colspan="3" style="padding:10px;">Nincs a szűrésnek megfelelő találat.</td></tr>';
                            }
                        });
                }
    
                function escapeHtml(text) {
                    return text.replace(/[&<>"']/g, function(m) {
                        return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m];
                    });
                }
    
                // Eseményfigyelők a dinamikus kereséshez
                nameInput.addEventListener('input', fetchItems);
                urlInput.addEventListener('input', fetchItems);
                typeInput.addEventListener('input', fetchItems);
    
                // Első betöltés
                fetchItems();
            });
            </script>
            <?php
            return ob_get_clean();
        }
    
        // AJAX Szűrés Lekérdezése
        public function ajax_filter_items() {
            global $wpdb;
    
            $name = isset($_GET['name']) ? sanitize_text_field($_GET['name']) : '';
            $url = isset($_GET['url']) ? sanitize_text_field($_GET['url']) : '';$type = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : '';
    
            $query = "SELECT name, url, type FROM {$this->table_name} WHERE 1=1";
            $args = array();
    
            if (!empty($name)) {$query .= " AND name LIKE %s";
                $args[] = '%' . $wpdb->esc_like($name) . '%';
            }
            if (!empty($url)) {$query .= " AND url LIKE %s";
                $args[] = '%' . $wpdb->esc_like($url) . '%';
            }
            if (!empty($type)) {$query .= " AND type LIKE %s";
                $args[] = '%' . $wpdb->esc_like($type) . '%';
            }
    
            $query .= " ORDER BY name ASC";
    
            if (!empty($args)) {$results = $wpdb->get_results($wpdb->prepare($query,$args));
            } else {
                $results = $wpdb->get_results($query);
            }
    
            wp_send_json($results);
        }
    }
    
    new CustomItemsManager();
  • Készítsünk Playwright project-et Copilot-tal

    Készítsünk Playwright project-et Copilot-tal

    Megkésve, de törve nem, én is elkezdtem egyre több dologra használni az AI-t, ahogy az a korábbi bejegyzéseimből is látszik. Reggel gondoltam egy merészet, és megkértem a Copilot-ot, hogy kezdjünk el csinálni egy Playwright, TypeScript projectet, mindezt VSCode-ban, és csak CLI-jal. Meg kell mondjam, egész jól sikerültek az alapok, és időben is lényegesen hamarabb készült el, mintsem, ha kézzel kezdem el.

    Felbuzdulva ezen, megkértem, hogy integrálja bele az Allure riport készítést, ami elsőre nem volt teljes siker, mert nem tartalmazta a korábbi futásokat a kérésem ellenére, de ez is hamar javítva lett.

    Folytattam azzal, hogy egy ingyenes és publikus API hívásra írattam vele 3 tesztet, majd pedig kértem, hogy legyen 2 negatív is, amit ő elsőre úgy értett, hogy átírta a meglévőket. Ezt is hamar javítottuk.

    Kértem, hogy amit csináltunk, azt tegye bele egy Word fájlba, illetve még készítettem vele egy readme.md fájlt is. Szerintem egész jól sikerült, mindezt relatív kevés idő alatt, szóval biztosan fogom még folytatni vele a munkát.

    Íme a teljes lista:

    Project path: D:\Repositories\ai-playwright
    Earliest retrievable project record:
    approximately 08:55 local time (06:55 UTC)

    08:53 ? Initial workspace session

    • Opened the working area under D:\Repositories.

    08:55 ? Created the Playwright project

    • Created D:\Repositories\ai-playwright as a TypeScript Playwright project.
    • Configured Chromium, added example tests and a GitHub Actions workflow.
    • Installed dependencies and Chromium.
    • Verified that the two starter tests passed with npx playwright test.

    09:01 ? Changed reporting to Allure

    • Switched the default Playwright reporter to Allure.
    • Added Allure report generation and execution-history persistence.
    • Updated package.json, playwright.config.ts, .github/workflows/playwright.yml, .gitignore, and scripts/allure-history.js.
    • Configured CI to restore and save Allure history.
    • Verified the initial tests and report generation.

    09:07?09:23 ? Opened and fixed Allure history

    • Generated allure-report and started it with npm run allure:open.
    • Investigated why previous executions were not visible.
    • Fixed the issue by clearing old result files before each run while preserving history.
    • Verified that the report showed three executions per test instead of treating earlier executions as retries.

    09:26 ? Added Open-Meteo API tests

    • Added tests in tests/open-meteo-api.spec.ts.
    • Covered a valid forecast request, invalid latitude, and invalid longitude.
    • All three tests passed.

    09:28?09:34 ? Regenerated the report and improved assertions

    • Regenerated Allure with the API tests.
    • Added descriptive failure messages to the expect assertions.

    09:49?09:51 ? Added more negative API scenarios

    • Added a request missing required longitude.
    • Added a request with an unsupported current-weather variable.
    • The API test file reached five tests total.

    09:52 ? Verified all tests

    • Ran the full suite.
    • All seven tests passed.
    • Regenerated the Allure report with all tests.

    09:55?10:03 ? Fixed Allure generation in GitHub Actions

    • Investigated why the report was not generated in CI.
    • Updated the workflow to restore history, run Playwright, always generate the report, and upload the report artifact when possible.
    • Local verification passed.

    10:07?10:12 ? Discussed opening reports from CI

    • Clarified that GitHub Actions cannot directly open a browser on the runner.
    • Discussed GitHub Pages as the way to automatically host and view the report.
    • Explained how to open a downloaded report locally with npx allure open.

    10:20 ? Opened the Allure report again

    10:31 ? Added Playwright Codegen

    • Added the codegen script to package.json.
    • Usage: npm run codegen — https://example.com.
    • Discussed saving recorded tests with –output tests/recorded.spec.ts.

    10:34?10:48 ? Reviewed the project and exported coverage

    • Explained that the project is a Playwright test project, not the application under test.
    • Identified that baseURL and webServer are not configured.
    • Created D:\Repositories\ai-playwright\test-coverage.xlsx.
    • The workbook contains all seven test cases, behaviors, assertions, Allure statuses, and a test-scope coverage summary.

    Note: The earliest project-creation record available is the summarized 08:55 local-time session; the initial setup session does not contain a complete message-by-message transcript.

    10:56 – Added project README documentation

    • Created README.md in the project root.
    • Documented prerequisites and installation from scratch.
    • Documented browser and API test execution.
    • Documented Allure report generation, opening, and execution-history handling.
    • Documented Playwright Codegen usage.
    • Documented project configuration, structure, GitHub Actions CI behavior, and troubleshooting.
  • Helyesírás és elütés

    Helyesírás és elütés

    Megkértem az AI-t, hogy nézze át a blogban szereplő bejegyzéseket, és keressen helyesírási hibákat, elütéseket. Elég sokat talált. Látszik, hogy sosem volt erősségem.

    Az a helyzet, hogy egyre több olyan feladatot találok neki, ami nekem igen sok időmet emésztené fel.

    A hqa.hu/blog felületén található bejegyzésekből és kivonatokból az alábbi helyesírási hibák, elütések, különírási/egybeírási tévesztések, valamint hiányzó ékezetek gyűjthetők össze:

    1. Különírási és egybeírási hibák

    • „Blog bejegyzések” → Blogbejegyzések
      • Hiba: Összetett szóként egybeírandó.
    • „Energia ellátási probléma” → Energiaellátási probléma
      • Hiba: Az energiaellátási összetett szó, egybeírandó (a szótagszáma és összetételi tagjai alapján is).
    • „energia felhasználásunkat” → energiafelhasználásunkat
      • Hiba: Összetett szó, egybeírandó.
    • „másfelöl” → másfelől(és külön/egybeírás/ékezet)
    • „végig néztem” → végignéztem
      • Hiba: Az igekötő (végig) közvetlenül az ige előtt áll, így egybeírandó.
    • „realitás érzését” → realitásérzését
      • Hiba: Jelöletlen birtokos összetétel, egybeírandó.
    • „mégis nincs kéznel egy darab példa kódom sem” → példakódom
      • Hiba: Összetett szó, egybeírandó.
    • „válasz kód” → válaszkód
      • Hiba: Egybeírandó összetett szó.
    • „működő képes” → működőképes
      • Hiba: Egybeírandó melléknév.
    • „próbálta egy IDE-n belül kezelni a dolgokat (…) viszont egy idő után kezdet átláthatatlanná válni” → (lásd lent a nyelvtani hibáknál is)

    2. Elütések és nyelvtani / alaktani hibák

    • „Azt hiszem, érdemes lesz ezt kipróbálom valamikor…”
      • Hiba: Helyesen: kipróbálnom (főnévi igeneves szerkezet/határozott ragozás hiánya: ezt kipróbálnom).
    • „…ámbár nem gyakran fordul elő, hogy a semmiből kell építkezi…”
      • Hiba: Elütés: építkezni (hiányzik az n betű).
    • „…amire én számítók…”
      • Hiba: Elütés: számítok (rövid o).
    • „…de ez az AI téma nagyon érdekel változásokat tartogat.”
      • Hiba: Értelmezési/fogalmazási elütés (valószínűleg: „nagyon sok változást tartogat” vagy „érdekes változásokat tartogat”).
    • „…kezdet átláthatatlanná válni…”
      • Hiba: Elütés: kezdett (múlt időben két t betű).
    • „mégis nincs kéznel…”
      • Hiba: Hiányzó ékezet / elütés: kéznél.
    • „…virtuális gépemről futottak a tesztek… irtam én is meg”
      • Hiba: Hiányzó ékezet: írtam.
    • „szerintem a videóban említett pontokat…” / „Majd kifejtem videóban említett…”
      • Hiba: Hiányzó névelő: „kifejtem a videóban említett…”.

    3. Hiányzó ékezetek és idegen szavak magyarítása

    • „definicióját” → definícióját
      • Hiba: A definíció szóban a második i hosszú.
    • „vélemenyem” → véleményem
      • Hiba: Hiányzó ékezet.
    • „sztenderdet” (vagy standardot)
      • Megjegyzés: Bár a sztenderd használható magyarított alak, a szakmai szövegekben gyakran keveredik a fonetikus és az angol helyesírással (standard).

    Összegzés

    A blogbejegyzésekben a leggyakrabban előforduló hibatípusok a különírt összetett szavak (pl. blog bejegyzés, válasz kód, példa kód, energia felhasználás), az igekötős igék különírása (végig néztem), valamint az apróbb gépelési elütések / ragozási tévesztések (kipróbálom → kipróbálnom, építkezi → építkezni, kezdet → kezdett).